Compare commits

..

330 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
Juan Ignacio Sánchez Lara
94e38cef9d Update instructions 2015-06-30 11:00:32 +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
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
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
59dfd11e5b Remove geom_type retrieval 2015-02-10 16:57:43 +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
66 changed files with 5838 additions and 4366 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/

98
.jshintrc Normal file
View File

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

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,24 +18,24 @@ config.status--test:
config/environments/test.js: config.status--test
./config.status--test
check-local: config/environments/test.js
./run_tests.sh ${RUNTESTFLAGS} \
test/unit/cartodb/*.js \
test/unit/cartodb/cache/model/*.js \
test/integration/*.js \
test/acceptance/*.js \
test/acceptance/cache/*.js
test: config/environments/test.js
@echo "***tests***"
@$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} \
test/unit/cartodb/*.js \
test/unit/cartodb/cache/model/*.js \
test/integration/*.js \
test/acceptance/*.js \
test/acceptance/cache/*.js
check-submodules:
PATH="$$PATH:$(srcdir)/node_modules/.bin/"; \
for sub in windshaft grainstore node-varnish mapnik; do \
if test -e node_modules/$${sub}; then \
echo "Testing submodule $${sub}"; \
make -C node_modules/$${sub} check || exit 1; \
fi; \
done
jshint:
@echo "***jshint***"
@./node_modules/.bin/jshint lib/ test/ app.js
check-full: check-local check-submodules
test-all: jshint test
check: check-local
coverage:
@RUNTESTFLAGS=--with-coverage make test
check: test
.PHONY: pre-install test jshint coverage

611
NEWS.md

File diff suppressed because it is too large Load Diff

108
README.md
View File

@@ -1,39 +1,38 @@
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.js >=0.8
- npm >=1.2.1
- PostgreSQL >8.3.x, PostGIS >1.5.x
- Redis >2.4.0 (http://www.redis.io)
- Mapnik 2.0.1, 2.0.2, 2.1.0, 2.2.0, 2.3.0. See Installing Mapnik.
- Windshaft: check [Windshaft dependencies and installation notes](https://github.com/CartoDB/Windshaft#dependencies)
- libcairo2-dev, libpango1.0-dev, libjpeg8-dev and libgif-dev for server side canvas support
[core]
- node-0.8.x+
- PostgreSQL-8.3+
- PostGIS-1.5.0+
- Redis 2.4.0+ (http://www.redis.io)
- Mapnik 2.0 or 2.1
- For cache control (optional)
- CartoDB-SQL-API 1.0.0+
- CartoDB 0.9.5+ (for `CDB_QueryTables`)
- Varnish (http://www.varnish-cache.org)
[for cache control]
- CartoDB-SQL-API 1.0.0+
- CartoDB 0.9.5+ (for ``CDB_QueryTables``)
- Varnish (http://www.varnish-cache.org)
[for running the testsuite]
- Imagemagick (http://www.imagemagick.org)
- For running the testsuite
- ImageMagick (http://www.imagemagick.org)
Configure
---------
@@ -60,6 +59,14 @@ 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.
Upgrading
---------
Checkout your commit/branch. If you need to reinstall dependencies (you can check [NEWS](NEWS.md)) do the following:
```
rm -rf node_modules; npm install
```
Run
---
@@ -75,59 +82,16 @@ there may be out-of-sync records in there.
Take a look: http://redis.io/commands
URLs
----
Documentation
-------------
**TILES**
[GET] subdomain.cartodb.com/tiles/:table_name/:z/:x/:y.[png|png8|grid.json]
Args:
* sql - plain SQL arguments
* interactivity - specify the column to use in UTFGrid
* cache_buster - Specify an identifier for the internal tile cache.
Requesting tiles with the same cache_buster value may
result in being served a cached version of the tile
(even when requesting a tile for the first time, as tiles
can be prepared in advance)
* cache_policy - Set to "persist" to have the server send an Cache-Control
header requesting caching devices to keep the response
cached as much as possible. This is best used with a
timestamp value in cache_buster for manual control of
updates.
* geom_type - override the cartodb default
* style - override the default map style with Carto
The [docs directory](https://github.com/CartoDB/Windshaft-cartodb/tree/master/docs) contains different documentation
resources, from higher level to more detailed ones:
The [Maps API](https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md) defined the endpoints and their
expected parameters and outputs.
**STYLE**
Examples
--------
[GET/POST] subdomain.cartodb.com/tiles/:table_name/style
Args:
* style - the style in CartoCSS you want to set
* style_version - the version of the style for POST
* style_convert - request conversion to target version (both POST and GET)
**INFOWINDOW**
[GET] subdomain.cartodb.com/tiles/:table_name/infowindow
Args:
* infowindow - returns contents of infowindow from CartoDB.
**MAP METADATA**
[GET] subdomain.cartodb.com/tiles/:table_name/map_metadata
Args:
* infowindow - returns contents of infowindow from CartoDB.
All GET requests are wrappable with JSONP using callback argument,
including the UTFGrid map tile call.
[CartoDB's Map Gallery](http://cartodb.com/gallery/) showcases several examples of visualisations built on top of this.

70
app.js
View File

@@ -7,17 +7,21 @@
* environments: [development, production]
*/
var path = require('path'),
fs = require('fs'),
RedisPool = require('redis-mpool')
;
var path = require('path');
var fs = require('fs');
var RedisPool = require('redis-mpool');
var _ = require('underscore');
var ENV;
if ( process.argv[2] ) {
ENV = process.argv[2];
} else if ( process.env.NODE_ENV ) {
ENV = process.env.NODE_ENV;
} else {
ENV = 'development';
}
if ( process.argv[2] ) ENV = process.argv[2];
else if ( process.env['NODE_ENV'] ) ENV = process.env['NODE_ENV'];
else ENV = 'development';
process.env['NODE_ENV'] = ENV;
process.env.NODE_ENV = ENV;
// sanity check
if (ENV != 'development' && ENV != 'production' && ENV != 'staging' ){
@@ -26,14 +30,12 @@ if (ENV != 'development' && ENV != 'production' && ENV != 'staging' ){
process.exit(1);
}
var _ = require('underscore');
// set environment specific variables
global.environment = require(__dirname + '/config/environments/' + ENV);
global.environment.api_hostname = require('os').hostname().split('.')[0];
global.log4js = require('log4js');
log4js_config = {
var log4js_config = {
appenders: [],
replaceConsole:true
};
@@ -60,25 +62,22 @@ 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);
var redisOpts = _.defaults(global.environment.redis, {
name: 'windshaft',
unwatchOnRelease: false,
noReadyCheck: true
});
var redisPool = new RedisPool(redisOpts);
// Include cartodb_windshaft only _after_ the "global" variable is set
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/28
var CartodbWindshaft = require('./lib/cartodb/cartodb_windshaft'),
var cartodbWindshaft = require('./lib/cartodb/cartodb_windshaft'),
serverOptions = require('./lib/cartodb/server_options')(redisPool);
ws = CartodbWindshaft(serverOptions);
var ws = cartodbWindshaft(serverOptions);
if (global.statsClient) {
redisPool.on('status', function(status) {
@@ -100,29 +99,20 @@ ws.listen(global.environment.port, global.environment.host);
var version = require("./package").version;
ws.on('listening', function() {
console.log("Windshaft tileserver " + version + " started on "
+ global.environment.host + ':' + global.environment.port
+ " (" + ENV + ")");
});
// DEPRECATED, use SIGUSR2
process.on('SIGUSR1', function() {
console.log('WARNING: handling of SIGUSR1 by Windshaft-CartoDB is deprecated, please send SIGUSR2 instead');
ws.dumpCacheStats();
});
process.on('SIGUSR2', function() {
ws.dumpCacheStats();
console.log(
"Windshaft tileserver %s started on %s:%s (%s)",
version, global.environment.host, global.environment.port, ENV
);
});
process.on('SIGHUP', function() {
global.log4js.clearAndShutdownAppenders(function() {
global.log4js.configure(log4js_config);
global.logger = log4js.getLogger();
global.logger = global.log4js.getLogger();
console.log('Log files reloaded');
});
});
process.on('uncaughtException', function(err) {
logger.error('Uncaught exception: ' + err.stack);
global.logger.error('Uncaught exception: ' + err.stack);
});

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.
@@ -14,13 +17,11 @@ var config = {
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x
,base_url_templated: '(?:/api/v1/map/named|/tiles/template)'
,base_url_templated: '(?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)'
// Base url for the Detached Maps API
// "maps" is the the new API,
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)'
// Base url for the Inline Maps and Table Maps API
,base_url_legacy: '/tiles/:table'
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Maximum number of connections for one process
// 128 is a good value with a limit of 1024 open file descriptors
@@ -86,13 +87,62 @@ 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,
// 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
}
},
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
@@ -131,27 +181,9 @@ var config = {
},
emitter: {
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
}
}
,sqlapi: {
protocol: 'http',
// If "host" is given, it will be used
// to connect to the SQL-API without a
// DNS lookup
host: '127.0.0.1',
port: 8080,
// The "domain" part will be appended to
// the cartodb username and passed to
// SQL-API requests in the Host HTTP header
domain: 'localhost.lan',
version: 'v1',
// Maximum lenght of SQL query for GET
// requests. Longer queries will be sent
// using POST. Defaults to 2048
max_get_sql_length: 2048,
// Maximum time to wait for a response,
// in milliseconds. Defaults to 100.
timeout: 100
},
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
}
,varnish: {
host: 'localhost',
@@ -162,6 +194,15 @@ var config = {
ttl: 86400,
layergroupTtl: 86400 // the max-age for cache-control header in layergroup responses
}
// this [OPTIONAL] configuration enables invalidating by surrogate key in fastly
,fastly: {
// whether the invalidation is enabled or not
enabled: false,
// the fastly api key
apiKey: 'wadus_api_key',
// the service that will get surrogate key invalidation
serviceId: 'wadus_service_id'
}
// If useProfiler is true every response will be served with an
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
@@ -174,6 +215,15 @@ var config = {
x: 0,
y: 0
}
,disabled_file: 'pids/disabled'
// Use this as a feature flags enabling/disabling mechanism
,enabledFeatures: {
// whether it should intercept tile render errors an act based on them, enabled by default.
onTileErrorStrategy: true,
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
}
};
module.exports = config;

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.
@@ -14,13 +17,11 @@ var config = {
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x
,base_url_templated: '(?:/api/v1/map/named|/tiles/template)'
,base_url_templated: '(?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)'
// Base url for the Detached Maps API
// "maps" is the the new API,
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)'
// Base url for the Inline Maps and Table Maps API
,base_url_legacy: '/tiles/:table'
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Maximum number of connections for one process
// 128 is a good value with a limit of 1024 open file descriptors
@@ -80,13 +81,62 @@ 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,
// 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
}
},
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
@@ -125,27 +175,9 @@ var config = {
},
emitter: {
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
}
}
,sqlapi: {
protocol: 'https',
// If "host" is given, it will be used
// to connect to the SQL-API without a
// DNS lookup
//host: '127.0.0.1',
port: 8080,
// The "domain" part will be appended to
// the cartodb username and passed to
// SQL-API requests in the Host HTTP header
domain: 'cartodb.com',
version: 'v2',
// Maximum lenght of SQL query for GET
// requests. Longer queries will be sent
// using POST. Defaults to 2048
max_get_sql_length: 2048,
// Maximum time to wait for a response,
// in milliseconds. Defaults to 100.
timeout: 100
},
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
}
,varnish: {
host: 'localhost',
@@ -156,6 +188,15 @@ var config = {
ttl: 86400,
layergroupTtl: 86400 // the max-age for cache-control header in layergroup responses
}
// this [OPTIONAL] configuration enables invalidating by surrogate key in fastly
,fastly: {
// whether the invalidation is enabled or not
enabled: false,
// the fastly api key
apiKey: 'wadus_api_key',
// the service that will get surrogate key invalidation
serviceId: 'wadus_service_id'
}
// If useProfiler is true every response will be served with an
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
@@ -166,15 +207,6 @@ var config = {
https: 'cartocdn.global.ssl.fastly.net'
}
}
// Optional rollbar support
,rollbar: {
token: 'secret',
// See http://github.com/rollbar/node_rollbar#configuration-reference
options: {
endpoint: 'https://api.rollbar.com/api/1/',
handler: 'inline'
}
}
// Settings for the health check available at /health
,health: {
enabled: true,
@@ -183,6 +215,15 @@ var config = {
x: 0,
y: 0
}
,disabled_file: 'pids/disabled'
// Use this as a feature flags enabling/disabling mechanism
,enabledFeatures: {
// whether it should intercept tile render errors an act based on them, enabled by default.
onTileErrorStrategy: true,
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
}
};
module.exports = config;

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.
@@ -14,13 +17,11 @@ var config = {
// Base url for the Templated Maps API
// "/api/v1/maps/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x
,base_url_templated: '(?:/api/v1/maps/named|/tiles/template)'
,base_url_templated: '(?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)'
// Base url for the Detached Maps API
// "/api/v1/maps" is the the new API,
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/maps|/tiles/layergroup)'
// Base url for the Inline Maps and Table Maps API
,base_url_legacy: '/tiles/:table'
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Maximum number of connections for one process
// 128 is a good value with a limit of 1024 open file descriptors
@@ -80,13 +81,62 @@ 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,
// 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
}
},
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
@@ -125,27 +175,9 @@ var config = {
},
emitter: {
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
}
}
,sqlapi: {
protocol: 'https',
// If "host" is given, it will be used
// to connect to the SQL-API without a
// DNS lookup
//host: '127.0.0.1',
port: 8080,
// The "domain" part will be appended to
// the cartodb username and passed to
// SQL-API requests in the Host HTTP header
domain: 'cartodb.com',
version: 'v2',
// Maximum lenght of SQL query for GET
// requests. Longer queries will be sent
// using POST. Defaults to 2048
max_get_sql_length: 2048,
// Maximum time to wait for a response,
// in milliseconds. Defaults to 100.
timeout: 100
},
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
}
,varnish: {
host: 'localhost',
@@ -156,6 +188,15 @@ var config = {
ttl: 86400,
layergroupTtl: 86400 // the max-age for cache-control header in layergroup responses
}
// this [OPTIONAL] configuration enables invalidating by surrogate key in fastly
,fastly: {
// whether the invalidation is enabled or not
enabled: false,
// the fastly api key
apiKey: 'wadus_api_key',
// the service that will get surrogate key invalidation
serviceId: 'wadus_service_id'
}
// If useProfiler is true every response will be served with an
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
@@ -166,15 +207,6 @@ var config = {
https: 'cartocdn.global.ssl.fastly.net'
}
}
// Optional rollbar support
,rollbar: {
token: 'secret',
// See http://github.com/rollbar/node_rollbar#configuration-reference
options: {
endpoint: 'https://api.rollbar.com/api/1/',
handler: 'inline'
}
}
// Settings for the health check available at /health
,health: {
enabled: false,
@@ -183,6 +215,15 @@ var config = {
x: 0,
y: 0
}
,disabled_file: 'pids/disabled'
// Use this as a feature flags enabling/disabling mechanism
,enabledFeatures: {
// whether it should intercept tile render errors an act based on them, enabled by default.
onTileErrorStrategy: true,
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
}
};
module.exports = config;

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.
@@ -14,13 +17,11 @@ var config = {
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x
,base_url_templated: '(?:/api/v1/map/named|/tiles/template)'
,base_url_templated: '(?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)'
// Base url for the Detached Maps API
// "maps" is the the new API,
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)'
// Base url for the Inline Maps and Table Maps API
,base_url_legacy: '/tiles/:table'
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Maximum number of connections for one process
// 128 is a good value with a limit of 1024 open file descriptors
@@ -80,13 +81,62 @@ 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,
// 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
}
},
http: {
timeout: 2000, // the timeout in ms for a http tile request
proxy: undefined, // the url for a proxy server
whitelist: [ // the whitelist of urlTemplates that can be used
'.*', // will enable any URL
'http://{s}.example.com/{z}/{x}/{y}.png',
// for testing purposes
'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png'
@@ -127,29 +177,9 @@ var config = {
},
emitter: {
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
}
}
,sqlapi: {
protocol: 'http',
// If "host" is given, it will be used
// to connect to the SQL-API without a
// DNS lookup
host: '127.0.0.1',
port: 1080,
// The "domain" part will be appended to
// the cartodb username and passed to
// SQL-API requests in the Host HTTP header
domain: 'donot_look_this_up',
// This port will be used by "make check" for testing purposes
// It must be available
version: 'v1',
// Maximum lenght of SQL query for GET
// requests. Longer queries will be sent
// using POST. Defaults to 2048
max_get_sql_length: 2048,
// Maximum time to wait for a response,
// in milliseconds. Defaults to 100.
timeout: 100
},
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
}
,varnish: {
host: '',
@@ -160,6 +190,15 @@ var config = {
ttl: 86400,
layergroupTtl: 86400 // the max-age for cache-control header in layergroup responses
}
// this [OPTIONAL] configuration enables invalidating by surrogate key in fastly
,fastly: {
// whether the invalidation is enabled or not
enabled: false,
// the fastly api key
apiKey: 'wadus_api_key',
// the service that will get surrogate key invalidation
serviceId: 'wadus_service_id'
}
// If useProfiler is true every response will be served with an
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
@@ -172,6 +211,15 @@ var config = {
x: 0,
y: 0
}
,disabled_file: 'pids/disabled'
// Use this as a feature flags enabling/disabling mechanism
,enabledFeatures: {
// whether it should intercept tile render errors an act based on them, enabled by default.
onTileErrorStrategy: true,
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
}
};
module.exports = config;

View File

@@ -18,7 +18,7 @@ Here is an example of how to create an anonymous map with JavaScript:
```javascript
var mapconfig = {
"version": "1.0.1",
"version": "1.3.1",
"layers": [{
"type": "cartodb",
"options": {
@@ -34,10 +34,10 @@ $.ajax({
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: 'http://documentation.cartodb.com/api/v1/map',
url: 'https://documentation.cartodb.com/api/v1/map',
data: JSON.stringify(mapconfig),
success: function(data) {
var templateUrl = 'http://documentation.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
var templateUrl = 'https://documentation.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
console.log(templateUrl);
}
})
@@ -57,7 +57,7 @@ The following map config sets up a map of European countries that have a white f
},
"layergroup": {
"layers": [{
"type": "cartodb",
"type": "mapnik",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
@@ -79,24 +79,33 @@ To get the `URL` to fetch the tiles you need to instantiate the map, where `temp
<div class="code-title notitle code-request"></div>
```bash
curl -X POST 'http://{account}.cartodb.com/api/v1/map/named/:template_id' -H 'Content-Type: application/json'
curl -X POST 'https://{account}.cartodb.com/api/v1/map/named/:template_id' -H 'Content-Type: application/json'
```
The response will return JSON with properties for the `layergroupid` and the timestamp (`last_updated`) of the last data modification.
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.
Here is an example response:
```javascript
{
"layergroupid": "c01a54877c62831bb51720263f91fb33:0",
"last_updated": "1970-01-01T00:00:00.000Z"
"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
http://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
```
## General Concepts
@@ -107,7 +116,7 @@ The following concepts are the same for every endpoint in the API except when it
By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a named map).
To execute an authorized request, api_key=YOURAPIKEY should be added to the request URL. The param can be also passed as POST param. We **strongly advise** using HTTPS when you are performing requests that include your `api_key`.
To execute an authorized request, `api_key=YOURAPIKEY` should be added to the request URL. The param can be also passed as POST param. Using HTTPS is mandatory when you are performing requests that include your `api_key`.
### Errors
@@ -144,9 +153,9 @@ POST /api/v1/map
```javascript
{
"version": "1.0.1",
"version": "1.3.0",
"layers": [{
"type": "cartodb",
"type": "mapnik",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
@@ -157,7 +166,7 @@ POST /api/v1/map
}
```
Should be a [Mapconfig](https://github.com/CartoDB/Windshaft/blob/0.19.1/doc/MapConfig-1.1.0.md).
Should be a [Mapconfig](https://github.com/CartoDB/Windshaft/blob/0.44.1/doc/MapConfig-1.3.0.md).
#### Response
@@ -167,14 +176,15 @@ The response includes:
The ID for that map, used to compose the URL for the tiles. The final URL is:
```html
http://{account}.cartodb.com/api/v1/map/:layergroupid/{z}/{x}/{y}.png
https://{account}.cartodb.com/api/v1/map/:layergroupid/{z}/{x}/{y}.png
```
- **updated_at**
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.
- **metadata**
Includes information about the layers.
-
- **cdn_url**
URLs to fetch the data using the best CDN for your zone.
@@ -183,14 +193,22 @@ The response includes:
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl 'http://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
curl 'https://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
```
<div class="code-title">RESPONSE</div>
```javascript
{
"layergroupid":"c01a54877c62831bb51720263f91fb33:0",
"last_updated":"1970-01-01T00:00:00.000Z"
"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"
@@ -198,31 +216,81 @@ curl 'http://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application
}
```
The tiles can be accessed using:
##### Retrieve resources from the layergroup
###### Mapnik tiles can be accessed using:
These tiles will get just the mapnik layers. To get individual layers see next section.
```bash
http://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
```
For UTF grid tiles:
###### 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
http://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/{z}/{x}/{y}.grid.json
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/{z}/{x}/{y}.grid.json
```
For attributes defined in `attributes` section:
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
http://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/attributes/:feature_id
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/1/{z}/{x}/{y}.torque.json
```
###### Attributes defined in `attributes` section:
```bash
https://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 }
{ "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. 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.
###### Blending and layer selection
```bash
https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/: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://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/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://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/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
@@ -272,7 +340,7 @@ Anonymous maps cannot be removed by an API call. They will expire after about fi
## 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: you can create named maps from private data and that users without an API Key can see them even though they are from that private data.
Named maps are essentially the same as anonymous maps except the MapConfig is stored on the server and the map is given a unique name. Two other big differences are: you can create named maps from private data and that users without an API Key can see them even though they are from that private data.
The main two differences compared to anonymous maps are:
@@ -280,7 +348,7 @@ The main two differences compared to anonymous maps are:
This allows you to control who is able to see the map based on a token auth
- **templates**
Since the mapconfig is static it can contain some variables so the client can modify the map's appearance using those variables.
Since the MapConfig is static it can contain some variables so the client can modify the map's appearance using those variables.
Template maps are persistent with no preset expiration. They can only be created or deleted by a CartoDB user with a valid API_KEY (see auth section).
@@ -331,18 +399,41 @@ POST /api/v1/map/named
}
}
]
},
"view": {
"zoom": 4,
"center": {
"lng": 0,
"lat": 0
},
"bounds": {
"west": -45,
"south": -45,
"east": 45,
"north": 45
}
}
}
```
##### 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 (_).
- **name**: There can be at most _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 (_).
- **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. Variables not provided at instantiation time trigger an error. A default is required for optional variables. Type specification is used for quoting, to avoid injections see template format section below.
- **layergroup**: the layer list definition. This is the MapConfig explained in anonymous maps. See [MapConfig documentation](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.1.0.md) for more info.
- **layergroup**: the layer list definition. This is the MapConfig explained in anonymous maps. See [MapConfig documentation](https://github.com/CartoDB/Windshaft/blob/0.44.1/doc/MapConfig-1.3.0.md) for more info.
- **view** (optional): extra keys to specify the compelling 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.
- **zoom** The zoom level to use
- **center**
- **lng** The longitude to use for the center
- **lat** The latitude to use for the center
- **bounds**
- **west**: LowerCorner longitude for the bounding box, in decimal degrees (aka most western)
- **south**: LowerCorner latitude for the bounding box, in decimal degrees (aka most southern)
- **east**: UpperCorner longitude for the bounding box, in decimal degrees (aka most eastern)
- **north**: UpperCorner latitude for the bounding box, in decimal degrees (aka most northern)
#### Template Format
@@ -438,7 +529,7 @@ curl -X POST \
<div class="code-title">Error</div>
```javascript
{
"error": "Some error string here"
"errors" : ["Some error string here"]
}
```
@@ -539,7 +630,7 @@ If a template with the same name does NOT exist, a 400 HTTP response is generate
```javascript
{
"error": "error string here"
"errors" : ["error string here"]
}
```
@@ -568,7 +659,7 @@ curl -X DELETE 'https://documentation.cartodb.com/api/v1/map/named/:template_nam
<div class="code-title">RESPONSE</div>
```javascript
{
"error": "Some error string here"
"errors" : ["Some error string here"]
}
```
@@ -606,7 +697,7 @@ curl -X GET 'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
<div class="code-title">ERROR</div>
```javascript
{
"error": "Some error string here"
"errors" : ["Some error string here"]
}
```
@@ -642,7 +733,7 @@ curl -X GET 'https://documentation.cartodb.com/api/v1/map/named/:template_name?a
<div class="code-title">ERROR</div>
```javascript
{
"error": "Some error string here"
"errors" : ["Some error string here"]
}
```
@@ -672,13 +763,15 @@ cartodb.createLayer('map_dom_id',layerSource)
1. [layer.setParams()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
2. [layer.setAuthToken()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
##Static Maps API
## Static Maps API
The Static Maps API can be initiated using both named and anonymous maps using the 'layergroupid' token. The API can be used to create static images of parts of maps and thumbnails for use in web design, graphic design, print, field work, and many other applications that require standard image formats.
### Maps API endpoints
Begin by instantiating either a named or anonymous map using the `layergroupid token` as demonstrated in the Maps API documentation above. The `layergroupsid token` calls to the map and allows for parameters in the definition to generate static images.
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
@@ -726,6 +819,25 @@ Note: you can see this endpoint as:
GET /api/v1/map/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`
```
#### Named map
##### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/static/named/:name/:width/:height.:format
```
##### Params
* **: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`
* **jpg** will have a default quality of 85.
A named maps static image will get its constraints from the [view in the template](#Arguments), if `view` is not present it will estimate the extent based on the involved tables otherwise it fallback 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.
@@ -768,6 +880,8 @@ By manipulating the `"urlTemplate"` custom basemaps can be used in generating st
**CartoDB**
As described in the [Mapconfig documentation](https://github.com/CartoDB/Windshaft/blob/0.44.1/doc/MapConfig-1.3.0.md), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility.
```javascript
{
"type": "cartodb",
@@ -779,17 +893,18 @@ By manipulating the `"urlTemplate"` custom basemaps can be used in generating st
},
```
Additoinally, static images from Torque maps and other map layers can be used together to generate highly customizable and versatile static maps.
Additionally, static images from Torque maps and other map layers can be used together to generate highly customizable and versatile static maps.
####Caching
#### 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
#### Limits
* While images can encompass an entirety of a map, the default limit for pixel range is 8192 x 8192.
* Resolution limitations for jpegs are at default a quality of 85. Limitations for quality are restricted to a maximum of XXX dpi.
* Image resolution by default is set to 72 DPI
* JPEG quality by default is 85%
* Timeout limits for generating static maps are the same across the CartoDB Editor and Platform. It is important to ensure timely processing of queries.
@@ -802,16 +917,17 @@ After instantiating a map from a CartoDB account:
GET /api/v1/map/static/center/4b615ff367e498e770e7d05e99181873:1420231989550.8699/14/40.71502926732618/-73.96039009094238/600/400.png
```
####Response
<div clas="wrap"><p class="wrap-border"><img src="https://raw.githubusercontent.com/namessanti/Pictures/master/static_api.png" alt="static-api"/></p>,</div>
#### Response
####MapConfig
<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-alpha",
"version": "1.3.0",
"layers": [
{
"type": "http",

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

104
docs/Routes.md Normal file
View File

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

View File

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

View File

@@ -1,10 +1,5 @@
var sqlApi = require('../sql/sql_api');
var PSQL = require('cartodb-psql');
var Step = require('step');
function QueryTablesApi(pgConnection, metadataBackend) {
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
function QueryTablesApi(pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
var affectedTableRegexCache = {
@@ -19,9 +14,9 @@ module.exports = QueryTablesApi;
QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, sql, callback) {
var query = 'SELECT CDB_QueryTables($windshaft$' + prepareSql(sql) + '$windshaft$)';
var query = 'SELECT CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$)';
this.runQuery(username, query, handleAffectedTablesInQueryRows, callback);
this.pgQueryRunner.run(username, query, handleAffectedTablesInQueryRows, callback);
};
function handleAffectedTablesInQueryRows(err, rows, callback) {
@@ -30,9 +25,9 @@ function handleAffectedTablesInQueryRows(err, rows, callback) {
callback(new Error('could not fetch source tables: ' + msg));
return;
}
var qtables = rows[0].cdb_querytables;
var tableNames = qtables.split(/^\{(.*)\}$/)[1];
tableNames = tableNames ? tableNames.split(',') : [];
// This is an Array, so no need to split into parts
var tableNames = rows[0].cdb_querytablestext;
callback(null, tableNames);
}
@@ -40,14 +35,14 @@ QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (usernam
var query = [
'WITH querytables AS (',
'SELECT * FROM CDB_QueryTables($windshaft$' + prepareSql(sql) + '$windshaft$) as tablenames',
'SELECT * FROM CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$) as tablenames',
')',
'SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max',
'FROM CDB_TableMetadata m',
'WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'
].join(' ');
this.runQuery(username, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
this.pgQueryRunner.run(username, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
};
function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
@@ -59,8 +54,8 @@ function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
var result = rows[0];
var tableNames = result.tablenames.split(/^\{(.*)\}$/)[1];
tableNames = tableNames ? tableNames.split(',') : [];
// This is an Array, so no need to split into parts
var tableNames = result.tablenames;
var lastUpdatedTime = result.max || 0;
@@ -70,63 +65,6 @@ function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
});
}
QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, callback) {
var self = this;
if (shouldQueryPostgresDirectly()) {
var params = {};
Step(
function setAuth() {
self.pgConnection.setDBAuth(username, params, this);
},
function setConn(err) {
if (err) {
throw err;
}
self.pgConnection.setDBConn(username, params, this);
},
function executeQuery(err) {
if (err) {
throw err;
}
var psql = new PSQL({
user: params.dbuser,
pass: params.dbpass,
host: params.dbhost,
port: params.dbport,
dbname: params.dbname
});
psql.query(query, function(err, resultSet) {
resultSet = resultSet || {};
var rows = resultSet.rows || [];
queryHandler(err, rows, callback);
});
}
);
} else {
Step(
function getApiKey() {
self.metadataBackend.getUserMapKey(username, this);
},
function executeQuery(err, apiKey) {
if (err) {
throw err;
}
sqlApi.query(username, apiKey, query, function(err, rows) {
queryHandler(err, rows, callback);
});
}
);
}
};
function prepareSql(sql) {
return sql
.replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
@@ -135,10 +73,3 @@ function prepareSql(sql) {
.replace(affectedTableRegexCache.pixel_height, '1')
;
}
function shouldQueryPostgresDirectly() {
return global.environment
&& global.environment.enabledFeatures
&& global.environment.enabledFeatures.cdbQueryTablesFromPostgres;
}

View File

@@ -0,0 +1,54 @@
function TablesExtentApi(pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = TablesExtentApi;
/**
* Given a username and a list of tables it will return the estimated extent in SRID 4326 for all the tables based on
* the_geom_webmercator (SRID 3857) column.
*
* @param {String} username
* @param {Array} tableNames The named can be schema qualified, so this accepts both `schema_name.table_name` and
* `table_name` format as valid input
* @param {Function} callback function(err, result) {Object} result with `west`, `south`, `east`, `north`
*/
TablesExtentApi.prototype.getBounds = function (username, tableNames, callback) {
var estimatedExtentSQLs = tableNames.map(function(tableName) {
var schemaTable = tableName.split('.');
if (schemaTable.length > 1) {
return "ST_EstimatedExtent('" + schemaTable[0] + "', '" + schemaTable[1] + "', 'the_geom_webmercator')";
}
return "ST_EstimatedExtent('" + schemaTable[0] + "', 'the_geom_webmercator')";
});
var query = [
"WITH ext as (" +
"SELECT ST_Transform(ST_SetSRID(ST_Extent(ST_Union(ARRAY[",
estimatedExtentSQLs.join(','),
"])), 3857), 4326) geom)",
"SELECT",
"ST_XMin(geom) west,",
"ST_YMin(geom) south,",
"ST_XMax(geom) east,",
"ST_YMax(geom) north",
"FROM ext"
].join(' ');
this.pgQueryRunner.run(username, query, handleBoundsResult, callback);
};
function handleBoundsResult(err, rows, callback) {
if (err){
var msg = err.message ? err.message : err;
callback(new Error('could not fetch source tables: ' + msg));
return;
}
var result = null;
if (rows.length > 0) {
result = {
bounds: rows[0]
};
}
callback(null, result);
}

View File

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

View File

@@ -0,0 +1,44 @@
var PSQL = require('cartodb-psql');
var step = require('step');
function PgQueryRunner(pgConnection) {
this.pgConnection = pgConnection;
}
module.exports = PgQueryRunner;
PgQueryRunner.prototype.run = function(username, query, queryHandler, callback) {
var self = this;
var params = {};
step(
function setAuth() {
self.pgConnection.setDBAuth(username, params, this);
},
function setConn(err) {
if (err) {
throw err;
}
self.pgConnection.setDBConn(username, params, this);
},
function executeQuery(err) {
if (err) {
throw err;
}
var psql = new PSQL({
user: params.dbuser,
pass: params.dbpass,
host: params.dbhost,
port: params.dbport,
dbname: params.dbname
});
psql.query(query, function(err, resultSet) {
resultSet = resultSet || {};
var rows = resultSet.rows || [];
queryHandler(err, rows, callback);
});
}
);
};

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, softPurge) {
this.serviceId = serviceId;
this.fastlyPurge = new FastlyPurge(apiKey, { softPurge: softPurge || true });
}
module.exports = FastlyCacheBackend;
/**
* @param cacheObject should respond to `key() -> String` method
* @param {Function} callback
*/
FastlyCacheBackend.prototype.invalidate = function(cacheObject, callback) {
this.fastlyPurge.key(this.serviceId, cacheObject.key(), callback);
};

View File

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

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;
@@ -22,5 +24,18 @@ SurrogateKeysCache.prototype.tag = function(response, cacheObject) {
* @param {Function} callback
*/
SurrogateKeysCache.prototype.invalidate = function(cacheObject, callback) {
this.cacheBackend.invalidate(cacheObject, callback);
var invalidationQueue = queue(this.cacheBackends.length);
this.cacheBackends.forEach(function(cacheBackend) {
invalidationQueue.defer(function(cacheBackend, done) {
cacheBackend.invalidate(cacheObject, done);
}, cacheBackend);
});
invalidationQueue.awaitAll(function(err, result) {
if (err) {
return callback(err);
}
callback(null, result);
});
};

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,12 +1,16 @@
var _ = require('underscore');
var Step = require('step');
var step = require('step');
var Windshaft = require('windshaft');
var Cache = require('./cache_validator');
var os = require('os');
var HealthCheck = require('./monitoring/health_check');
if ( ! process.env['PGAPPNAME'] )
process.env['PGAPPNAME']='cartodb_tiler';
var SurrogateKeysCache = require('./cache/surrogate_keys_cache');
var NamedMapsCacheEntry = require('./cache/model/named_maps_entry');
var VarnishHttpCacheBackend = require('./cache/backend/varnish_http');
var FastlyCacheBackend = require('./cache/backend/fastly');
if ( ! process.env.PGAPPNAME )
process.env.PGAPPNAME='cartodb_tiler';
var CartodbWindshaft = function(serverOptions) {
// Perform keyword substitution in statsd
@@ -18,55 +22,54 @@ var CartodbWindshaft = function(serverOptions) {
}
}
var redisPool = serverOptions.redis.pool
|| require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:cartodb'}));
var redisPool = serverOptions.redis.pool ||
require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:cartodb'}));
var cartoData = require('cartodb-redis')({pool: redisPool});
var templateMaps = serverOptions.templateMaps;
if(serverOptions.cache_enabled) {
console.log("cache invalidation enabled, varnish on ", serverOptions.varnish_host, ' ', serverOptions.varnish_port);
Cache.init(serverOptions.varnish_host, serverOptions.varnish_port, serverOptions.varnish_secret);
serverOptions.afterStateChange = function(req, data, callback) {
Cache.invalidate_db(req.params.dbname, req.params.table);
callback(null, data);
}
}
serverOptions.beforeStateChange = function(req, callback) {
var err = null;
if ( ! req.params.hasOwnProperty('_authorizedByApiKey') ) {
err = new Error("map state cannot be changed by unauthenticated request!");
}
callback(err, req);
};
// This is for Templated maps
//
// "named" is the official, "template" is for backward compatibility up to 1.6.x
//
var template_baseurl = global.environment.base_url_templated || '(?:/maps/named|/tiles/template)';
var SurrogateKeysCache = require('./cache/surrogate_keys_cache'),
NamedMapsCacheEntry = require('./cache/model/named_maps_entry'),
VarnishHttpCacheBackend = require('./cache/backend/varnish_http'),
varnishHttpCacheBackend = new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port),
surrogateKeysCache = new SurrogateKeysCache(varnishHttpCacheBackend);
var surrogateKeysCacheBackends = [];
if (serverOptions.varnish_purge_enabled) {
function invalidateNamedMap(owner, templateName) {
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
if (err) {
console.warn('Cache: surrogate key invalidation failed');
}
});
}
surrogateKeysCacheBackends.push(
new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port)
);
}
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, invalidateNamedMap);
if (!!serverOptions.fastly.enabled && !!serverOptions.fastly.apiKey && !!serverOptions.fastly.serviceId) {
surrogateKeysCacheBackends.push(
new FastlyCacheBackend(serverOptions.fastly.apiKey, serverOptions.fastly.serviceId)
);
}
var surrogateKeysCache = new SurrogateKeysCache(surrogateKeysCacheBackends);
function invalidateNamedMap (owner, templateName) {
var startTime = Date.now();
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
var logMessage = JSON.stringify({
username: owner,
type: 'named_map_invalidation',
elapsed: Date.now() - startTime,
error: !!err ? JSON.stringify(err.message) : undefined
});
if (err) {
console.warn(logMessage);
} else {
console.info(logMessage);
}
});
}
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, invalidateNamedMap);
});
// boot
var ws = new Windshaft.Server(serverOptions);
@@ -84,8 +87,12 @@ var CartodbWindshaft = function(serverOptions) {
// POST/PUT/DELETE requests are never cached anyway.
var noCacheGETRoutes = [
'/',
'/version',
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
serverOptions.base_url_mapconfig,
serverOptions.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format',
template_baseurl,
template_baseurl + '/:template_id',
template_baseurl + '/:template_id/jsonp'
];
ws.sendResponse = function(res, args) {
@@ -102,7 +109,7 @@ var CartodbWindshaft = function(serverOptions) {
}
}
var req = res.req;
Step (
step (
function addCacheChannel() {
if ( ! req ) {
// having no associated request can happen when
@@ -124,10 +131,11 @@ var CartodbWindshaft = function(serverOptions) {
//console.log("Skipping cache channel in route:\n" + req.route.path);
return false;
}
//console.log("Adding cache channel to route\n" + req.route.path + " not matching any in:\n" + mapCreateRoutes.join("\n"));
//console.log("Adding cache channel to route\n" + req.route.path + " not matching any in:\n" +
// mapCreateRoutes.join("\n"));
serverOptions.addCacheChannel(that, req, this);
},
function sendResponse(err, added) {
function sendResponse(err/*, added*/) {
if ( err ) console.log(err + err.stack);
ws_sendResponse.apply(that, thatArgs);
return null;
@@ -150,73 +158,35 @@ var CartodbWindshaft = function(serverOptions) {
* Routing
******************************************************************************************************************/
var TemplateMapsController = require('./controllers/template_maps'),
templateMapsController = new TemplateMapsController(
var NamedMapsController = require('./controllers/named_maps'),
namedMapsController = new NamedMapsController(
ws,
serverOptions,
templateMaps,
cartoData,
template_baseurl,
surrogateKeysCache,
NamedMapsCacheEntry,
serverOptions.pgConnection
surrogateKeysCache
);
templateMapsController.register(ws);
namedMapsController.register(ws);
var TablesExtentApi = require('./api/tables_extent_api');
var tablesExtentApi = new TablesExtentApi(serverOptions.pgQueryRunner);
var NamedStaticMapsController = require('./controllers/named_static_maps');
var namedStaticMapsController = new NamedStaticMapsController(
ws,
serverOptions,
templateMaps,
ws.staticMapBackend,
surrogateKeysCache,
tablesExtentApi
);
namedStaticMapsController.register(ws);
/*******************************************************************************************************************
* END Routing
******************************************************************************************************************/
/**
* Helper to allow access to the layer to be used in the maps infowindow popup.
*/
ws.get(serverOptions.base_url + '/infowindow', function(req, res){
ws.doCORS(res);
Step(
function(){
serverOptions.getInfowindow(req, this);
},
function(err, data){
if (err){
ws.sendError(res, {error: err.message}, 500, 'GET INFOWINDOW', err);
//ws.sendResponse(res, [{error: err.message}, 500]);
} else {
ws.sendResponse(res, [{infowindow: data}, 200]);
}
}
);
});
/**
* Helper to allow access to metadata to be used in embedded maps.
*/
ws.get(serverOptions.base_url + '/map_metadata', function(req, res){
ws.doCORS(res);
Step(
function(){
serverOptions.getMapMetadata(req, this);
},
function(err, data){
if (err){
ws.sendError(res, {error: err.message}, 500, 'GET MAP_METADATA', err);
//ws.sendResponse(res, [err.message, 500]);
} else {
ws.sendResponse(res, [{map_metadata: data}, 200]);
}
}
);
});
/**
* Helper API to allow per table tile cache (and sql cache) to be invalidated remotely.
* Keep endpoint for backwards compatibility
*/
ws.del(serverOptions.base_url + '/flush_cache', function(req, res){
ws.doCORS(res);
ws.sendResponse(res, [{status: 'ok'}, 200]);
});
var healthCheck = new HealthCheck(cartoData, Windshaft.tilelive);
ws.get('/health', function(req, res) {
var healthConfig = global.environment.health || {};

View File

@@ -0,0 +1,372 @@
var step = require('step');
var assert = require('assert');
var _ = require('underscore');
var templateName = require('../template_maps').templateName;
var CdbRequest = require('../models/cdb_request');
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
function NamedMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache) {
this.app = app;
this.serverOptions = serverOptions;
this.templateMaps = templateMaps;
this.metadataBackend = metadataBackend;
this.templateBaseUrl = templateBaseUrl;
this.surrogateKeysCache = surrogateKeysCache;
}
module.exports = NamedMapsController;
var cdbRequest = new CdbRequest();
NamedMapsController.prototype.register = function(app) {
app.get(this.templateBaseUrl + '/:template_id/jsonp', this.jsonp.bind(this));
app.post(this.templateBaseUrl, this.create.bind(this));
app.put(this.templateBaseUrl + '/:template_id', this.update.bind(this));
app.get(this.templateBaseUrl + '/:template_id', this.retrieve.bind(this));
app.del(this.templateBaseUrl + '/:template_id', this.destroy.bind(this));
app.get(this.templateBaseUrl, this.list.bind(this));
app.options(this.templateBaseUrl + '/:template_id', this.options.bind(this));
app.post(this.templateBaseUrl + '/:template_id', this.instantiate.bind(this));
};
// Add a template
NamedMapsController.prototype.create = function(req, res) {
var self = this;
this.app.doCORS(res);
var cdbuser = cdbRequest.userByReq(req);
step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function addTemplate(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated users can get template maps');
ifInvalidContentType(req, 'template POST data must be of type application/json');
var cfg = req.body;
self.templateMaps.addTemplate(cdbuser, cfg, this);
},
function prepareResponse(err, tpl_id){
assert.ifError(err);
return { template_id: tpl_id };
},
finishFn(self.app, res, 'POST TEMPLATE')
);
};
// Update a template
NamedMapsController.prototype.update = function(req, res) {
var self = this;
this.app.doCORS(res);
var cdbuser = cdbRequest.userByReq(req);
var template;
var tpl_id;
step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function updateTemplate(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated user can update templated maps');
ifInvalidContentType(req, 'template PUT data must be of type application/json');
template = req.body;
tpl_id = templateName(req.params.template_id);
self.templateMaps.updTemplate(cdbuser, tpl_id, template, this);
},
function prepareResponse(err){
assert.ifError(err);
return { template_id: tpl_id };
},
finishFn(self.app, res, 'PUT TEMPLATE')
);
};
// Get a specific template
NamedMapsController.prototype.retrieve = function(req, res) {
var self = this;
if (req.profiler) {
req.profiler.start('windshaft-cartodb.get_template');
}
this.app.doCORS(res);
var cdbuser = cdbRequest.userByReq(req);
var tpl_id;
step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function updateTemplate(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated users can get template maps');
tpl_id = templateName(req.params.template_id);
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
},
function prepareResponse(err, tpl_val) {
if ( err ) throw err;
if ( ! tpl_val ) {
err = new Error("Cannot find template '" + tpl_id + "' of user '" + cdbuser + "'");
err.http_status = 404;
throw err;
}
// auth_id was added by ourselves,
// so we remove it before returning to the user
delete tpl_val.auth_id;
return { template: tpl_val };
},
finishFn(self.app, res, 'GET TEMPLATE')
);
};
// Delete a specific template
NamedMapsController.prototype.destroy = function(req, res) {
var self = this;
if (req.profiler) {
req.profiler.start('windshaft-cartodb.delete_template');
}
this.app.doCORS(res);
var cdbuser = cdbRequest.userByReq(req);
var tpl_id;
step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function updateTemplate(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated users can delete template maps');
tpl_id = templateName(req.params.template_id);
self.templateMaps.delTemplate(cdbuser, tpl_id, this);
},
function prepareResponse(err/*, tpl_val*/){
if ( err ) throw err;
return { status: 'ok' };
},
finishFn(self.app, res, 'DELETE TEMPLATE', ['', 204])
);
};
// Get a list of owned templates
NamedMapsController.prototype.list = function(req, res) {
var self = this;
if ( req.profiler ) {
req.profiler.start('windshaft-cartodb.get_template_list');
}
this.app.doCORS(res);
var cdbuser = cdbRequest.userByReq(req);
step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function listTemplates(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated user can list templated maps');
self.templateMaps.listTemplates(cdbuser, this);
},
function prepareResponse(err, tpl_ids){
assert.ifError(err);
return { template_ids: tpl_ids };
},
finishFn(self.app, res, 'GET TEMPLATE LIST')
);
};
NamedMapsController.prototype.instantiate = function(req, res) {
var self = this;
if (req.profiler) {
req.profiler.start('windshaft-cartodb.instance_template_post');
}
step(
function() {
ifInvalidContentType(req, 'template POST data must be of type application/json');
self.instantiateTemplate(req, res, req.body, this);
}, function(err, response) {
self.finish_instantiation(err, response, res);
}
);
};
NamedMapsController.prototype.options = function(req, res, next) {
this.app.doCORS(res, "Content-Type");
return next();
};
/**
* jsonp endpoint, allows to instantiate a template with a json call.
* callback query argument is mandatory
*/
NamedMapsController.prototype.jsonp = function(req, res) {
var self = this;
if (req.profiler) {
req.profiler.start('windshaft-cartodb.instance_template_get');
}
step(
function() {
if ( req.query.callback === undefined || req.query.callback.length === 0) {
throw new Error('callback parameter should be present and be a function name');
}
var config = {};
if(req.query.config) {
try {
config = JSON.parse(req.query.config);
} catch(e) {
throw new Error('badformed config parameter, should be a valid JSON');
}
}
self.instantiateTemplate(req, res, config, this);
}, function(err, response) {
self.finish_instantiation(err, response, res);
}
);
};
// Instantiate a template
NamedMapsController.prototype.instantiateTemplate = function(req, res, template_params, callback) {
var self = this;
this.app.doCORS(res);
var template;
var layergroup;
var fakereq; // used for call to createLayergroup
var cdbuser = cdbRequest.userByReq(req);
// Format of template_id: [<template_owner>]@<template_id>
var tpl_id = templateName(req.params.template_id);
var auth_token = req.query.auth_token;
step(
function getTemplate(){
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
},
function checkAuthorized(err, templateValue) {
if ( req.profiler ) req.profiler.done('getTemplate');
if ( err ) throw err;
if ( ! templateValue ) {
err = new Error("Template '" + tpl_id + "' of user '" + cdbuser + "' not found");
err.http_status = 404;
throw err;
}
template = templateValue;
var authorized = false;
try {
authorized = self.templateMaps.isAuthorized(template, auth_token);
} catch (err) {
// we catch to add http_status
err.http_status = 403;
throw err;
}
if ( ! authorized ) {
err = new Error('Unauthorized template instanciation');
err.http_status = 403;
throw err;
}
if (req.profiler) {
req.profiler.done('authorizedByCert');
}
return self.templateMaps.instance(template, template_params);
},
function prepareParams(err, instance){
if ( req.profiler ) req.profiler.done('TemplateMaps_instance');
if ( err ) throw err;
layergroup = instance;
fakereq = {
query: {},
params: {
user: req.params.user
},
headers: _.clone(req.headers),
context: _.clone(req.context),
method: req.method,
res: res,
profiler: req.profiler
};
self.serverOptions.setDBParams(cdbuser, fakereq.params, this);
},
function setApiKey(err){
if ( req.profiler ) req.profiler.done('setDBParams');
if ( err ) throw err;
self.metadataBackend.getUserMapKey(cdbuser, this);
},
function createLayergroup(err, val) {
if ( req.profiler ) req.profiler.done('getUserMapKey');
if ( err ) throw err;
fakereq.params.api_key = val;
self.app.createLayergroup(layergroup, fakereq, this);
},
function prepareResponse(err, layergroup) {
if ( err ) {
return callback(err, { errors: [''+err] });
}
var tplhash = self.templateMaps.fingerPrint(template).substring(0,8);
layergroup.layergroupid = cdbuser + '@' + tplhash + '@' + layergroup.layergroupid;
res.header('X-Layergroup-Id', layergroup.layergroupid);
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, template.name));
callback(null, layergroup);
}
);
};
NamedMapsController.prototype.finish_instantiation = function(err, response, res) {
if (err) {
var statusCode = 400;
response = { errors: [''+err] };
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}
this.app.sendError(res, response, statusCode, 'POST INSTANCE TEMPLATE', err);
} else {
this.app.sendResponse(res, [response, 200]);
}
};
function finishFn(app, res, description, okResponse) {
return function finish(err, response){
var statusCode = 200;
if (err) {
statusCode = 400;
response = { errors: ['' + err] };
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}
app.sendError(res, response, statusCode, description, err);
} else {
app.sendResponse(res, okResponse || [response, statusCode]);
}
};
}
function ifUnauthenticated(authenticated, description) {
if (authenticated !== 1) {
var err = new Error(description);
err.http_status = 403;
throw err;
}
}
function ifInvalidContentType(req, description) {
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' ) {
throw new Error(description);
}
}

View File

@@ -0,0 +1,248 @@
var step = require('step');
var assert = require('assert');
var templateName = require('../template_maps').templateName;
var CdbRequest = require('../models/cdb_request');
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
var _ = require('underscore');
function NamedStaticMapsController(app, serverOptions, templateMaps, staticMapBackend, surrogateKeysCache,
tablesExtentApi) {
this.app = app;
this.serverOptions = serverOptions;
this.templateMaps = templateMaps;
this.staticMapBackend = staticMapBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.tablesExtentApi = tablesExtentApi;
}
module.exports = NamedStaticMapsController;
var cdbRequest = new CdbRequest();
NamedStaticMapsController.prototype.register = function(app) {
app.get(app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format', this.named.bind(this));
};
NamedStaticMapsController.prototype.named = function(req, res) {
var self = this;
this.app.doCORS(res);
var cdbUser = cdbRequest.userByReq(req);
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
var template;
var layergroupConfig;
var layergroupId;
var fakeReq;
var cacheChannel;
step(
function reqParams() {
self.app.req2params(req, this);
},
function getTemplate(err) {
assert.ifError(err);
self.templateMaps.getTemplate(cdbUser, templateName(req.params.template_id), this);
},
function checkExists(err, tpl) {
assert.ifError(err);
if (!tpl) {
var notFoundErr = new Error(
"Template '" + templateName(req.params.template_id) + "' of user '" + cdbUser + "' not found"
);
notFoundErr.http_status = 404;
throw notFoundErr;
}
return tpl;
},
function checkAuthorized(err, tpl) {
assert.ifError(err);
var authorized = false;
try {
authorized = self.templateMaps.isAuthorized(tpl, req.query.auth_token);
} catch (err) {
// we catch to add http_status
var authorizationFailedErr = new Error('Failed to authorize template');
authorizationFailedErr.http_status = 403;
throw authorizationFailedErr;
}
if ( ! authorized ) {
var unauthorizedErr = new Error('Unauthorized template instantiation');
unauthorizedErr.http_status = 403;
throw unauthorizedErr;
}
return tpl;
},
function prepareParams(err, tpl) {
assert.ifError(err);
template = tpl;
var templateParams = {};
if (req.query.config) {
try {
templateParams = JSON.parse(req.query.config);
} catch (e) {
throw new Error('malformed config parameter, should be a valid JSON');
}
}
return templateParams;
},
function instantiateTemplate(err, templateParams) {
assert.ifError(err);
return self.templateMaps.instance(template, templateParams);
},
function prepareLayergroup(err, layergroup) {
assert.ifError(err);
layergroupConfig = layergroup;
fakeReq = {
query: {},
params: {
user: req.params.user
},
headers: _.clone(req.headers),
context: _.clone(req.context),
method: req.method,
res: res,
profiler: req.profiler
};
self.serverOptions.setDBParams(cdbUser, fakeReq.params, this);
},
function setApiKey(err){
assert.ifError(err);
self.app.createLayergroup(layergroupConfig, fakeReq, this);
},
function prepareResponse(err, layergroup) {
assert.ifError(err);
// added by createLayergroup
cacheChannel = res.header('X-Cache-Channel');
res.removeHeader('X-Cache-Channel');
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbUser, template.name));
layergroupId = layergroup.layergroupid.split(":")[0];
return null;
},
function staticImageOptions(err) {
assert.ifError(err);
getStaticImageOptions(template, this);
},
function estimateBounds(err, imageOpts) {
assert.ifError(err);
if (imageOpts) {
return imageOpts;
}
var defaultZoomCenter = {
zoom: 1,
center: {
lng: 0,
lat: 0
}
};
var dbTables = cacheChannel.split(':');
if (dbTables.length <= 1 || dbTables[1].length === 0) {
return defaultZoomCenter;
}
var tableNames = dbTables[1].split(',');
if (tableNames.length === 0) {
return defaultZoomCenter;
}
var next = this;
self.tablesExtentApi.getBounds(cdbUser, tableNames, function(err, result) {
next(null, result || defaultZoomCenter);
});
},
function getImage(err, imageOpts) {
assert.ifError(err);
var staticImageReq = {
headers: _.clone(fakeReq.headers),
params: _.extend(_.clone(fakeReq.params), {
token: layergroupId,
format: req.params.format
})
};
var width = +req.params.width;
var height = +req.params.height;
if (!_.isUndefined(imageOpts.zoom) && imageOpts.center) {
self.staticMapBackend.getImage(staticImageReq, width, height, imageOpts.zoom, imageOpts.center, this);
} else {
self.staticMapBackend.getImage(staticImageReq, width, height, imageOpts.bounds, this);
}
},
function handleImage(err, image, headers, stats) {
if (req.profiler) {
req.profiler.done('render-' + format);
req.profiler.add(stats || {});
}
if (err) {
if (!err.error) {
err.error = err.message;
}
self.app.sendError(res, err, self.app.findStatusCode(err), 'STATIC_VIZ_MAP', err);
} else {
res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format);
res.setHeader('Cache-Control', 'public,max-age=7200,must-revalidate');
self.app.sendResponse(res, [image, 200]);
}
}
);
};
function getStaticImageOptions(template, callback) {
if (template.view) {
var zoomCenter = templateZoomCenter(template.view);
if (zoomCenter) {
return callback(null, zoomCenter);
}
var bounds = templateBounds(template.view);
if (bounds) {
return callback(null, bounds);
}
}
return callback(null, null);
}
function templateZoomCenter(view) {
if (!_.isUndefined(view.zoom) && view.center) {
return {
zoom: view.zoom,
center: view.center
};
}
return false;
}
function templateBounds(view) {
if (view.bounds) {
var hasAllBounds = _.every(['west', 'south', 'east', 'north'], function(prop) {
return !!view.bounds[prop];
});
if (hasAllBounds) {
return {
bounds: {
west: view.bounds.west,
south: view.bounds.south,
east: view.bounds.east,
north: view.bounds.north
}
};
} else {
return false;
}
}
return false;
}

View File

@@ -1,479 +0,0 @@
var Step = require('step');
var _ = require('underscore');
function TemplateMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache,
NamedMapsCacheEntry, pgConnection) {
this.app = app;
this.serverOptions = serverOptions;
this.templateMaps = templateMaps;
this.metadataBackend = metadataBackend;
this.templateBaseUrl = templateBaseUrl;
this.surrogateKeysCache = surrogateKeysCache;
this.NamedMapsCacheEntry = NamedMapsCacheEntry;
this.pgConnection = pgConnection;
}
module.exports = TemplateMapsController;
TemplateMapsController.prototype.register = function(app) {
app.get(this.templateBaseUrl + '/:template_id/jsonp', this.jsonp.bind(this));
app.post(this.templateBaseUrl, this.create.bind(this));
app.put(this.templateBaseUrl + '/:template_id', this.update.bind(this));
app.get(this.templateBaseUrl + '/:template_id', this.retrieve.bind(this));
app.del(this.templateBaseUrl + '/:template_id', this.destroy.bind(this));
app.get(this.templateBaseUrl, this.list.bind(this));
app.options(this.templateBaseUrl + '/:template_id', this.options.bind(this));
app.post(this.templateBaseUrl + '/:template_id', this.instantiate.bind(this));
};
// Add a template
TemplateMapsController.prototype.create = function(req, res) {
var self = this;
this.app.doCORS(res);
var cdbuser = self.serverOptions.userByReq(req);
Step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function addTemplate(err, authenticated) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated user can create templated maps");
err.http_status = 403;
throw err;
}
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
throw new Error('template POST data must be of type application/json');
var cfg = req.body;
self.templateMaps.addTemplate(cdbuser, cfg, this);
},
function prepareResponse(err, tpl_id){
if ( err ) throw err;
// NOTE: might omit "cdbuser" if == dbowner ...
return { template_id: cdbuser + '@' + tpl_id };
},
function finish(err, response){
if ( req.profiler ) {
res.header('X-Tiler-Profiler', req.profiler.toJSONString());
}
if (err){
response = { error: ''+err };
var statusCode = 400;
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}
self.app.sendError(res, response, statusCode, 'POST TEMPLATE', err);
} else {
self.app.sendResponse(res, [response, 200]);
}
}
);
};
// Update a template
TemplateMapsController.prototype.update = function(req, res) {
var self = this;
this.app.doCORS(res);
var cdbuser = this.serverOptions.userByReq(req);
var template;
var tpl_id;
Step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function updateTemplate(err, authenticated) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated user can list templated maps");
err.http_status = 403;
throw err;
}
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
throw new Error('template PUT data must be of type application/json');
template = req.body;
tpl_id = req.params.template_id.split('@');
if ( tpl_id.length > 1 ) {
if ( tpl_id[0] != cdbuser ) {
err = new Error("Invalid template id '"
+ req.params.template_id + "' for user '" + cdbuser + "'");
err.http_status = 404;
throw err;
}
tpl_id = tpl_id[1];
}
self.templateMaps.updTemplate(cdbuser, tpl_id, template, this);
},
function prepareResponse(err){
if ( err ) throw err;
return { template_id: cdbuser + '@' + tpl_id };
},
function finish(err, response){
if ( req.profiler ) {
res.header('X-Tiler-Profiler', req.profiler.toJSONString());
}
if (err){
var statusCode = 400;
response = { error: ''+err };
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}
self.app.sendError(res, response, statusCode, 'PUT TEMPLATE', err);
} else {
self.app.sendResponse(res, [response, 200]);
}
}
);
};
// Get a specific template
TemplateMapsController.prototype.retrieve = function(req, res) {
var self = this;
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.get_template');
}
this.app.doCORS(res);
var cdbuser = this.serverOptions.userByReq(req);
var template;
var tpl_id;
Step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function updateTemplate(err, authenticated) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated users can get template maps");
err.http_status = 403;
throw err;
}
tpl_id = req.params.template_id.split('@');
if ( tpl_id.length > 1 ) {
if ( tpl_id[0] != cdbuser ) {
var err = new Error("Cannot get template id '"
+ req.params.template_id + "' for user '" + cdbuser + "'");
err.http_status = 404;
throw err;
}
tpl_id = tpl_id[1];
}
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
},
function prepareResponse(err, tpl_val){
if ( err ) throw err;
if ( ! tpl_val ) {
err = new Error("Cannot find template '" + tpl_id + "' of user '" + cdbuser + "'");
err.http_status = 404;
throw err;
}
// auth_id was added by ourselves,
// so we remove it before returning to the user
delete tpl_val.auth_id;
return { template: tpl_val };
},
function finish(err, response){
if (err){
var statusCode = 400;
response = { error: ''+err };
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}
self.app.sendError(res, response, statusCode, 'GET TEMPLATE', err);
} else {
self.app.sendResponse(res, [response, 200]);
}
}
);
};
// Delete a specific template
TemplateMapsController.prototype.destroy = function(req, res) {
var self = this;
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.delete_template');
}
this.app.doCORS(res);
var cdbuser = this.serverOptions.userByReq(req);
var template;
var tpl_id;
Step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function updateTemplate(err, authenticated) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated users can delete template maps");
err.http_status = 403;
throw err;
}
tpl_id = req.params.template_id.split('@');
if ( tpl_id.length > 1 ) {
if ( tpl_id[0] != cdbuser ) {
var err = new Error("Cannot find template id '"
+ req.params.template_id + "' for user '" + cdbuser + "'");
err.http_status = 404;
throw err;
}
tpl_id = tpl_id[1];
}
self.templateMaps.delTemplate(cdbuser, tpl_id, this);
},
function prepareResponse(err, tpl_val){
if ( err ) throw err;
return { status: 'ok' };
},
function finish(err, response){
if (err){
var statusCode = 400;
response = { error: ''+err };
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}
self.app.sendError(res, response, statusCode, 'DELETE TEMPLATE', err);
} else {
self.app.sendResponse(res, ['', 204]);
}
}
);
};
// Get a list of owned templates
TemplateMapsController.prototype.list = function(req, res) {
var self = this;
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.get_template_list');
}
this.app.doCORS(res);
var cdbuser = this.serverOptions.userByReq(req);
Step(
function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this);
},
function listTemplates(err, authenticated) {
if ( err ) throw err;
if (authenticated !== 1) {
err = new Error("Only authenticated user can list templated maps");
err.http_status = 403;
throw err;
}
self.templateMaps.listTemplates(cdbuser, this);
},
function prepareResponse(err, tpl_ids){
if ( err ) throw err;
// NOTE: might omit "cbduser" if == dbowner ...
var ids = _.map(tpl_ids, function(id) { return cdbuser + '@' + id; });
return { template_ids: ids };
},
function finish(err, response){
var statusCode = 200;
if (err){
response = { error: ''+err };
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}
self.app.sendError(res, response, statusCode, 'GET TEMPLATE LIST', err);
} else {
self.app.sendResponse(res, [response, statusCode]);
}
}
);
};
TemplateMapsController.prototype.instantiate = function(req, res) {
var self = this;
if ( req.profiler && req.profiler.statsd_client) {
req.profiler.start('windshaft-cartodb.instance_template_post');
}
Step(
function() {
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json') {
throw new Error('template POST data must be of type application/json, it is instead ');
}
self.instantiateTemplate(req, res, req.body, this);
}, function(err, response) {
self.finish_instantiation(err, response, res, req);
}
);
};
TemplateMapsController.prototype.options = function(req, res) {
this.app.doCORS(res, "Content-Type");
return next();
};
/**
* jsonp endpoint, allows to instantiate a template with a json call.
* callback query argument is mandatory
*/
TemplateMapsController.prototype.jsonp = function(req, res) {
var self = this;
if ( req.profiler && req.profiler.statsd_client) {
req.profiler.start('windshaft-cartodb.instance_template_get');
}
Step(
function() {
if ( req.query.callback === undefined || req.query.callback.length === 0) {
throw new Error('callback parameter should be present and be a function name');
}
var config = {};
if(req.query.config) {
try {
config = JSON.parse(req.query.config);
} catch(e) {
throw new Error('badformed config parameter, should be a valid JSON');
}
}
self.instantiateTemplate(req, res, config, this);
}, function(err, response) {
self.finish_instantiation(err, response, res, req);
}
);
};
// Instantiate a template
TemplateMapsController.prototype.instantiateTemplate = function(req, res, template_params, callback) {
var self = this;
this.app.doCORS(res);
var template;
var layergroup;
var fakereq; // used for call to createLayergroup
var cdbuser = self.serverOptions.userByReq(req);
// Format of template_id: [<template_owner>]@<template_id>
var tpl_id = req.params.template_id.split('@');
if ( tpl_id.length > 1 ) {
if ( tpl_id[0] && tpl_id[0] != cdbuser ) {
var err = new Error('Cannot instanciate map of user "'
+ tpl_id[0] + '" on database of user "'
+ cdbuser + '"');
err.http_status = 403;
callback(err);
return;
}
tpl_id = tpl_id[1];
}
var auth_token = req.query.auth_token;
Step(
function getTemplate(){
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
},
function checkAuthorized(err, templateValue) {
if ( req.profiler ) req.profiler.done('getTemplate');
if ( err ) throw err;
if ( ! templateValue ) {
err = new Error("Template '" + tpl_id + "' of user '" + cdbuser + "' not found");
err.http_status = 404;
throw err;
}
template = templateValue;
var authorized = false;
try {
authorized = self.templateMaps.isAuthorized(template, auth_token);
} catch (err) {
// we catch to add http_status
err.http_status = 403;
throw err;
}
if ( ! authorized ) {
err = new Error('Unauthorized template instanciation');
err.http_status = 403;
throw err;
}
if (req.profiler) {
req.profiler.done('authorizedByCert');
}
return self.templateMaps.instance(template, template_params);
},
function prepareParams(err, instance){
if ( req.profiler ) req.profiler.done('TemplateMaps_instance');
if ( err ) throw err;
layergroup = instance;
fakereq = { query: {}, params: {}, headers: _.clone(req.headers),
method: req.method,
res: res,
profiler: req.profiler
};
self.setDBParams(cdbuser, fakereq.params, this);
},
function setApiKey(err){
if ( req.profiler ) req.profiler.done('setDBParams');
if ( err ) throw err;
self.metadataBackend.getUserMapKey(cdbuser, this);
},
function createLayergroup(err, val) {
if ( req.profiler ) req.profiler.done('getUserMapKey');
if ( err ) throw err;
fakereq.params.api_key = val;
self.app.createLayergroup(layergroup, fakereq, this);
},
function prepareResponse(err, layergroup) {
if ( err ) {
throw err;
}
var tplhash = self.templateMaps.fingerPrint(template).substring(0,8);
layergroup.layergroupid = cdbuser + '@' + tplhash + '@' + layergroup.layergroupid;
self.surrogateKeysCache.tag(res, new self.NamedMapsCacheEntry(cdbuser, template.name));
return layergroup;
},
callback
);
};
TemplateMapsController.prototype.finish_instantiation = function(err, response, res, req) {
if ( req.profiler ) {
res.header('X-Tiler-Profiler', req.profiler.toJSONString());
}
if (err) {
var statusCode = 400;
response = { error: ''+err };
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}
if(global.environment.debug) {
response.stack = err.stack;
}
this.app.sendError(res, response, statusCode, 'POST INSTANCE TEMPLATE', err);
} else {
this.app.sendResponse(res, [response, 200]);
}
};
TemplateMapsController.prototype.setDBParams = function(cdbuser, params, callback) {
var self = this;
Step(
function setAuth() {
self.pgConnection.setDBAuth(cdbuser, params, this);
},
function setConn(err) {
if ( err ) throw err;
self.pgConnection.setDBConn(cdbuser, params, this);
},
function finish(err) {
callback(err);
}
);
};

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,26 @@
function CdbRequest() {
this.RE_USER_FROM_HOST = new RegExp(global.environment.user_from_host ||
'^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com"
);
}
module.exports = CdbRequest;
CdbRequest.prototype.userByReq = function(req) {
var host = req.headers.host;
if (req.params.user) {
return req.params.user;
}
var mat = host.match(this.RE_USER_FROM_HOST);
if ( ! mat ) {
console.error("Pattern '" + this.RE_USER_FROM_HOST + "' does not match hostname '" + host + "'");
return;
}
// console.log("Matches: "); console.dir(mat);
if ( mat.length !== 2 ) {
console.error("Pattern '" + this.RE_USER_FROM_HOST + "' gave unexpected matches against '" + host + "': ", mat);
return;
}
return mat[1];
};

View File

@@ -1,8 +1,5 @@
var _ = require('underscore'),
dot = require('dot'),
fs = require('fs'),
path = require('path'),
Step = require('step');
var fs = require('fs');
var step = require('step');
function HealthCheck(metadataBackend, tilelive) {
this.metadataBackend = metadataBackend;
@@ -12,24 +9,9 @@ function HealthCheck(metadataBackend, tilelive) {
module.exports = HealthCheck;
var mapnikOptions = {
query: {
metatile: 1,
poolSize: 4,
bufferSize: 64
},
protocol: 'mapnik:',
slashes: true,
xml: null
};
var xmlTemplate = dot.template(fs.readFileSync(path.resolve(__dirname, 'map-config.xml'), 'utf-8'));
HealthCheck.prototype.check = function(config, callback) {
var self = this,
startTime,
result = {
var result = {
redis: {
ok: false
},
@@ -40,50 +22,23 @@ HealthCheck.prototype.check = function(config, callback) {
ok: false
}
};
mapnikXmlParams = config;
Step(
function getDBParams() {
startTime = Date.now();
self.metadataBackend.getAllUserDBParams(config.username, this);
step(
function getManualDisable() {
fs.readFile(global.environment.disabled_file, this);
},
function loadMapnik(err, dbParams) {
if (err) {
throw err;
}
result.redis = {
ok: !err,
elapsed: Date.now() - startTime,
size: Object.keys(dbParams).length
};
mapnikOptions.xml = xmlTemplate(mapnikXmlParams);
startTime = Date.now();
self.tilelive.load(mapnikOptions, this);
function handleDisabledFile(err, data) {
var next = this;
if (err) {
return next();
}
if (!!data) {
err = new Error(data);
err.http_status = 503;
throw err;
}
},
function getTile(err, source) {
if (err) {
throw err;
}
result.mapnik = {
ok: !err,
elapsed: Date.now() - startTime
};
startTime = Date.now();
source.getTile(config.z, config.x, config.y, this);
},
function handleTile(err, tile) {
result.tile = {
ok: !err
};
if (tile) {
result.tile.elapsed = Date.now() - startTime;
result.tile.size = tile.length;
}
function handleResult(err) {
callback(err, result);
}
);

View File

@@ -1,53 +1,50 @@
var _ = require('underscore');
var Step = require('step');
var QueryTablesApi = require('./api/query_tables_api');
var PgConnection = require('./backends/pg_connection');
var crypto = require('crypto');
var step = require('step');
var LZMA = require('lzma').LZMA;
var assert = require('assert');
var RedisPool = require('redis-mpool');
var QueryTablesApi = require('./api/query_tables_api');
var PgQueryRunner = require('./backends/pg_query_runner');
var PgConnection = require('./backends/pg_connection');
var TemplateMaps = require('./template_maps.js');
var MapConfigNamedLayersAdapter = require('./models/mapconfig_named_layers_adapter');
var CdbRequest = require('./models/cdb_request');
// This is for backward compatibility with 1.3.3
if ( _.isUndefined(global.environment.sqlapi.domain) ) {
// Only use "host" as "domain" if it contains alphanumeric characters
var host = global.environment.sqlapi.host;
if ( host && host.match(/[a-zA-Z]/) ) {
global.environment.sqlapi.domain = host;
}
}
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
// Whitelist query parameters and attach format
var REQUEST_QUERY_PARAMS_WHITELIST = [
'sql',
'geom_type',
'cache_buster',
'cache_policy',
'callback',
'interactivity',
'config',
'map_key',
'api_key',
'auth_token',
'style',
'style_version',
'style_convert',
'config',
'scale_factor'
'callback'
];
module.exports = function(redisPool) {
redisPool = redisPool
|| require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:server_options'}));
redisPool = redisPool || new RedisPool(_.extend(global.environment.redis, {name: 'windshaft:server_options'}));
var cartoData = require('cartodb-redis')({ pool: redisPool }),
lzmaWorker = new LZMA(),
pgConnection = new PgConnection(cartoData),
queryTablesApi = new QueryTablesApi(pgConnection, cartoData);
var cartoData = require('cartodb-redis')({ pool: redisPool });
var lzmaWorker = new LZMA();
var pgConnection = new PgConnection(cartoData);
var pgQueryRunner = new PgQueryRunner(pgConnection);
var queryTablesApi = new QueryTablesApi(pgQueryRunner);
var cdbRequest = new CdbRequest();
var rendererConfig = _.defaults(global.environment.renderer || {}, {
cache_ttl: 60000, // milliseconds
metatile: 4,
bufferSize: 64,
statsInterval: 60000
statsInterval: 60000,
mapnik: {
poolSize: 8,
metatile: 2,
bufferSize: 64,
snapToGrid: false,
clipByBox2d: false,
limits: {}
},
http: {}
});
var me = {
@@ -73,13 +70,7 @@ module.exports = function(redisPool) {
cachedir: global.environment.millstone.cache_basedir,
mapnik_version: global.environment.mapnik_version,
mapnik_tile_format: global.environment.mapnik_tile_format || 'png',
default_layergroup_ttl: global.environment.mapConfigTTL || 7200,
gc_prob: 0.01 // @deprecated since Windshaft-1.8.0
},
mapnik: {
poolSize: rendererConfig.poolSize,
metatile: rendererConfig.metatile,
bufferSize: rendererConfig.bufferSize
default_layergroup_ttl: global.environment.mapConfigTTL || 7200
},
statsd: global.environment.statsd,
renderCache: {
@@ -87,6 +78,7 @@ module.exports = function(redisPool) {
statsInterval: rendererConfig.statsInterval
},
renderer: {
mapnik: rendererConfig.mapnik,
http: rendererConfig.http
},
redis: global.environment.redis,
@@ -96,6 +88,7 @@ module.exports = function(redisPool) {
varnish_http_port: global.environment.varnish.http_port,
varnish_secret: global.environment.varnish.secret,
varnish_purge_enabled: global.environment.varnish.purge_enabled,
fastly: global.environment.fastly || {},
cache_enabled: global.environment.cache_enabled,
log_format: global.environment.log_format,
useProfiler: global.environment.useProfiler
@@ -110,6 +103,8 @@ module.exports = function(redisPool) {
// Re-use pgConnection
me.pgConnection = pgConnection;
// Re-use pgQueryRunner
me.pgQueryRunner = pgQueryRunner;
var templateMaps = new TemplateMaps(redisPool, {
max_user_templates: global.environment.maxUserTemplates
@@ -130,106 +125,81 @@ module.exports = function(redisPool) {
return dbName + ':' + tableNames.join(',');
};
me.generateMD5 = function(data){
var hash = crypto.createHash('md5');
hash.update(data);
return hash.digest('hex');
};
me.generateCacheChannel = function(app, req, callback){
// Build channelCache key
var dbName = req.params.dbname;
var cacheKey = [ dbName, req.params.token ].join(':');
// Build channelCache key
var dbName = req.params.dbname;
var cacheKey = [ dbName ];
if ( req.params.token ) cacheKey.push(req.params.token);
else if ( req.params.sql ) cacheKey.push( me.generateMD5(req.params.sql) );
cacheKey = cacheKey.join(':');
var that = this;
Step (
function checkCached() {
if ( me.channelCache.hasOwnProperty(cacheKey) ) {
callback(null, me.channelCache[cacheKey]);
return;
}
return null;
},
function extractSQL(err) {
if ( err ) throw err;
if ( req.params.token ) {
// TODO: cached cache channel for token-based access should
// be constructed at renderer cache creation time
// See http://github.com/CartoDB/Windshaft-cartodb/issues/152
if ( ! app.mapStore ) {
throw new Error('missing channel cache for token ' + req.params.token);
}
var next = this;
var mapStore = app.mapStore;
Step(
function loadFromStore() {
mapStore.load(req.params.token, this);
},
function getSQL(err, mapConfig) {
if (req.profiler) req.profiler.done('mapStore_load');
if ( err ) throw err;
var sql = [];
_.each(mapConfig.obj().layers, function(lyr) {
sql.push(lyr.options.sql);
});
sql = sql.join(';');
return sql;
},
function finish(err, sql) {
next(err, sql);
}
);
return;
}
if ( ! req.params.sql ) {
return null; // no sql
}
// We have sql, and no token...
// strip out windshaft/mapnik inserted sql if present
var sql = req.params.sql.match(/^\((.*)\)\sas\scdbq$/);
sql = (sql != null) ? sql[1] : req.params.sql;
return sql;
},
function findAffectedTables(err, sql) {
if ( err ) throw err;
if ( ! sql ) {
if ( ! req.params.table ) {
throw new Error("this request doesn't need an X-Cache-Channel generated");
}
return [req.params.table];
}
queryTablesApi.getAffectedTablesInQuery(that.userByReq(req), sql, this); // in addCacheChannel
},
function buildCacheChannel(err, tableNames) {
if ( err ) throw err;
if (req.profiler && ! req.params.table ) {
req.profiler.done('affectedTables');
}
var dbName = req.params.dbname;
var cacheChannel = me.buildCacheChannel(dbName,tableNames);
// store for caching from me.generateCacheChannel
// (not worth when table was specified in params)
if ( ! req.params.table ) {
me.channelCache[cacheKey] = cacheChannel;
}
return cacheChannel;
},
function finish(err, cacheChannel) {
callback(err, cacheChannel);
// no token means no tables associated
if (!req.params.token) {
return callback(null, this.buildCacheChannel(dbName, []));
}
);
step(
function checkCached() {
if ( me.channelCache.hasOwnProperty(cacheKey) ) {
return callback(null, me.channelCache[cacheKey]);
}
return null;
},
function extractSQL(err) {
assert.ifError(err);
// TODO: cached cache channel for token-based access should
// be constructed at renderer cache creation time
// See http://github.com/CartoDB/Windshaft-cartodb/issues/152
if ( ! app.mapStore ) {
throw new Error('missing channel cache for token ' + req.params.token);
}
var mapStore = app.mapStore;
step(
function loadFromStore() {
mapStore.load(req.params.token, this);
},
function getSQL(err, mapConfig) {
if (req.profiler) {
req.profiler.done('mapStore_load');
}
assert.ifError(err);
var queries = mapConfig.getLayers()
.map(function(lyr) {
return lyr.options.sql;
})
.filter(function(sql) {
return !!sql;
});
return queries.length ? queries.join(';') : null;
},
this
);
},
function findAffectedTables(err, sql) {
assert.ifError(err);
if ( ! sql ) {
throw new Error("this request doesn't need an X-Cache-Channel generated");
}
queryTablesApi.getAffectedTablesInQuery(cdbRequest.userByReq(req), sql, this); // in addCacheChannel
},
function buildCacheChannel(err, tableNames) {
assert.ifError(err);
if (req.profiler) {
req.profiler.done('affectedTables');
}
var cacheChannel = me.buildCacheChannel(dbName,tableNames);
me.channelCache[cacheKey] = cacheChannel;
return cacheChannel;
},
function finish(err, cacheChannel) {
callback(err, cacheChannel);
}
);
};
// Set the cache chanel info to invalidate the cache on the frontend server
@@ -244,11 +214,11 @@ module.exports = function(redisPool) {
me.addCacheChannel = function(app, req, cb) {
// skip non-GET requests, or requests for which there's no response
if ( req.method != 'GET' || ! req.res ) { cb(null, null); return; }
if (req.profiler) req.profiler.start('addCacheChannel');
if (req.profiler) {
req.profiler.start('addCacheChannel');
}
var res = req.res;
var cache_policy = req.query.cache_policy;
if ( req.params.token ) cache_policy = 'persist';
if ( cache_policy == 'persist' ) {
if ( req.params.token ) {
res.header('Cache-Control', 'public,max-age=31536000'); // 1 year
} else {
var ttl = global.environment.varnish.ttl || 86400;
@@ -267,8 +237,10 @@ module.exports = function(redisPool) {
res.header('Last-Modified', lastUpdated.toUTCString());
me.generateCacheChannel(app, req, function(err, channel){
if (req.profiler) req.profiler.done('generateCacheChannel');
if (req.profiler) req.profiler.end();
if (req.profiler) {
req.profiler.done('generateCacheChannel');
req.profiler.end();
}
if ( ! err ) {
res.header('X-Cache-Channel', channel);
cb(null, channel);
@@ -280,21 +252,62 @@ module.exports = function(redisPool) {
});
};
me.beforeLayergroupCreate = function(req, requestMapConfig, callback) {
mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, pgConnection, function(err, layers, datasource) {
if (err) {
return callback(err);
}
requestMapConfig.layers = layers;
return callback(null, requestMapConfig, datasource)
});
if (global.environment.enabledFeatures.onTileErrorStrategy !== false) {
me.renderer.onTileErrorStrategy = function(err, tile, headers, stats, format, callback) {
if (err && err.message === 'Render timed out' && format === 'png') {
return callback(null, timeoutErrorTile, { 'Content-Type': 'image/png' }, {});
} else {
return callback(err, tile, headers, stats);
}
};
}
me.renderCache.beforeRendererCreate = function(req, callback) {
var user = cdbRequest.userByReq(req);
var rendererOptions = {};
step(
function getLimits(err) {
assert.ifError(err);
cartoData.getTilerRenderLimit(user, this);
},
function handleTilerLimits(err, renderLimit) {
assert.ifError(err);
rendererOptions.limits = {
cacheOnTimeout: rendererConfig.mapnik.limits.cacheOnTimeout || false,
render: renderLimit || rendererConfig.mapnik.limits.render || 0
};
return null;
},
function finish(err) {
if (err) {
return callback(err);
}
return callback(null, rendererOptions);
}
);
};
me.beforeLayergroupCreate = function(req, requestMapConfig, callback) {
mapConfigNamedLayersAdapter.getLayers(cdbRequest.userByReq(req), requestMapConfig.layers, pgConnection,
function(err, layers, datasource) {
if (err) {
return callback(err);
}
requestMapConfig.layers = layers;
return callback(null, requestMapConfig, datasource);
}
);
};
me.afterLayergroupCreate = function(req, mapconfig, response, callback) {
var token = response.layergroupid;
var username = this.userByReq(req);
var username = cdbRequest.userByReq(req);
var tasksleft = 2; // redis key and affectedTables
var errors = [];
@@ -321,8 +334,12 @@ module.exports = function(redisPool) {
// take place before proceeding. Error will be logged
// asyncronously
cartoData.incMapviewCount(username, mapconfig.stat_tag, function(err) {
if (req.profiler) req.profiler.done('incMapviewCount');
if ( err ) console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
if (req.profiler) {
req.profiler.done('incMapviewCount');
}
if ( err ) {
console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
}
done();
});
@@ -333,31 +350,34 @@ module.exports = function(redisPool) {
var dbName = req.params.dbname;
var cacheKey = dbName + ':' + token;
Step(
step(
function getAffectedTablesAndLastUpdatedTime() {
queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
},
function handleAffectedTablesAndLastUpdatedTime(err, result) {
if (req.profiler) req.profiler.done('queryTablesAndLastUpdated');
if ( err ) throw err;
if (req.profiler) {
req.profiler.done('queryTablesAndLastUpdated');
}
assert.ifError(err);
var cacheChannel = me.buildCacheChannel(dbName, result.affectedTables);
me.channelCache[cacheKey] = cacheChannel;
if (req.res && req.method == 'GET') {
var res = req.res;
if ( req.query && req.query.cache_policy == 'persist' ) {
res.header('Cache-Control', 'public,max-age=31536000'); // 1 year
} else {
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
}
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();
var res = req.res;
if (res) {
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
res.header('Last-Modified', (new Date()).toUTCString());
res.header('X-Cache-Channel', cacheChannel);
}
res.header('X-Layergroup-Id', response.layergroupid);
}
return null;
},
function finish(err) {
@@ -368,28 +388,6 @@ module.exports = function(redisPool) {
/* X-Cache-Channel generation } */
me.re_userFromHost = new RegExp(
global.environment.user_from_host ||
'^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com"
);
me.userByReq = function(req) {
var host = req.headers.host;
var mat = host.match(this.re_userFromHost);
if ( ! mat ) {
console.error("ERROR: user pattern '" + this.re_userFromHost
+ "' does not match hostname '" + host + "'");
return;
}
// console.log("Matches: "); console.dir(mat);
if ( ! mat.length === 2 ) {
console.error("ERROR: pattern '" + this.re_userFromHost
+ "' gave unexpected matches against '" + host + "': " + mat);
return;
}
return mat[1];
};
// Check if a request is authorized by a signer
//
// @param req express request object
@@ -397,33 +395,29 @@ module.exports = function(redisPool) {
// null if the request is not signed by anyone
// or will be a string cartodb username otherwise.
//
me.authorizedBySigner = function(req, callback)
{
if ( ! req.params.token || ! req.params.signer ) {
//console.log("No signature provided"); // debugging
callback(null, null); // no signer requested
return;
}
var signer = req.params.signer;
var layergroup_id = req.params.token;
var auth_token = req.params.auth_token;
//console.log("Checking authorization from signer " + signer + " for resource " + layergroup_id + " with auth_token " + auth_token);
var mapStore = req.app.mapStore;
if (!mapStore) {
throw new Error('Unable to retrieve map configuration token');
}
mapStore.load(layergroup_id, function(err, mapConfig) {
if (err) {
throw err;
me.authorizedBySigner = function(req, callback) {
if ( ! req.params.token || ! req.params.signer ) {
return callback(null, null); // no signer requested
}
var authorized = me.templateMaps.isAuthorized(mapConfig.obj().template, auth_token);
callback(null, authorized ? signer : null);
});
var signer = req.params.signer;
var layergroup_id = req.params.token;
var auth_token = req.params.auth_token;
var mapStore = req.app.mapStore;
if (!mapStore) {
throw new Error('Unable to retrieve map configuration token');
}
mapStore.load(layergroup_id, function(err, mapConfig) {
if (err) {
return callback(err);
}
var authorized = me.templateMaps.isAuthorized(mapConfig.obj().template, auth_token);
return callback(null, authorized ? signer : null);
});
};
// Check if a request is authorized by api_key
@@ -444,13 +438,13 @@ module.exports = function(redisPool) {
return;
}
//console.log("given ApiKey: " + givenKey);
var user = me.userByReq(req);
Step(
var user = cdbRequest.userByReq(req);
step(
function (){
cartoData.getUserMapKey(user, this);
},
function checkApiKey(err, val){
if (err) throw err;
assert.ifError(err);
return ( val && givenKey == val ) ? 1 : 0;
},
function finish(err, authorized) {
@@ -467,15 +461,17 @@ module.exports = function(redisPool) {
*/
me.authorize = function(req, callback) {
var that = this;
var user = me.userByReq(req);
var user = cdbRequest.userByReq(req);
Step(
step(
function (){
that.authorizedByAPIKey(req, this);
},
function checkApiKey(err, authorized){
if (req.profiler) req.profiler.done('authorizedByAPIKey');
if (err) throw err;
if (req.profiler) {
req.profiler.done('authorizedByAPIKey');
}
assert.ifError(err);
// if not authorized by api_key, continue
if (authorized !== 1) {
@@ -485,28 +481,18 @@ module.exports = function(redisPool) {
return;
}
_.extend(req.params, { _authorizedByApiKey: true });
// authorized by api key, login as the given username and stop
pgConnection.setDBAuth(user, req.params, function(err) {
callback(err, true); // authorized (or error)
});
},
function checkSignAuthorized(err, signed_by){
if (err) throw err;
if (req.profiler) {
if ( req.params._authorizedByApiKey ) {
req.profiler.done('setDBAuth');
} else {
req.profiler.done('authorizedBySigner');
}
if (err) {
return callback(err);
}
if ( ! signed_by ) {
// request not authorized by signer.
// if table was given, continue to check table privacy
if ( req.params.table ) return null;
// if no signer name was given, let dbparams and
// PostgreSQL do the rest.
@@ -521,30 +507,32 @@ module.exports = function(redisPool) {
return;
}
// Authorized by "signed_by" !
_.extend(req.params, { _authorizedBySigner: signed_by });
pgConnection.setDBAuth(signed_by, req.params, function(err) {
if (req.profiler) req.profiler.done('setDBAuth');
if (req.profiler) {
req.profiler.done('setDBAuth');
}
callback(err, true); // authorized (or error)
});
},
function getDatabase(err){
if (err) throw err;
// NOTE: only used to get to table privacy
cartoData.getUserDBName(user, this);
},
function getPrivacy(err, dbname){
if (err) throw err;
if (req.profiler) req.profiler.done('tablePrivacy_getUserDBName');
cartoData.getTablePrivacy(dbname, req.params.table, this);
},
function(err, privacy){
if (req.profiler) req.profiler.done('getTablePrivacy');
callback(err, privacy !== "0");
}
);
};
me.setDBParams = function(cdbuser, params, callback) {
step(
function setAuth() {
pgConnection.setDBAuth(cdbuser, params, this);
},
function setConn(err) {
if ( err ) throw err;
pgConnection.setDBConn(cdbuser, params, this);
},
function finish(err) {
callback(err);
}
);
};
// jshint maxcomplexity:10
/**
* Whitelist input and get database name & default geometry type from
* subdomain/user metadata held in CartoDB Redis
@@ -555,17 +543,21 @@ module.exports = function(redisPool) {
if ( req.query.lzma ) {
// TODO: check ?
//console.log("type of req.query.lzma is " + typeof(req.query.lzma));
// Decode (from base64)
var lzma = (new Buffer(req.query.lzma, 'base64').toString('binary')).split('').map(function(c) { return c.charCodeAt(0) - 128 });
var lzma = new Buffer(req.query.lzma, 'base64')
.toString('binary')
.split('')
.map(function(c) {
return c.charCodeAt(0) - 128;
});
// Decompress
lzmaWorker.decompress(
lzma,
function(result) {
if (req.profiler) req.profiler.done('LZMA decompress');
if (req.profiler) {
req.profiler.done('lzma');
}
try {
delete req.query.lzma;
_.extend(req.query, JSON.parse(result));
@@ -573,38 +565,38 @@ module.exports = function(redisPool) {
} catch (err) {
callback(new Error('Error parsing lzma as JSON: ' + err));
}
},
function(percent) { // progress
//console.log("LZMA decompression " + percent + "%");
}
);
return;
}
var bad_query = _.difference(_.keys(req.query), REQUEST_QUERY_PARAMS_WHITELIST);
req.query = _.pick(req.query, REQUEST_QUERY_PARAMS_WHITELIST);
req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object
_.each(bad_query, function(key){ delete req.query[key]; });
req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object
var user = me.userByReq(req);
var user = cdbRequest.userByReq(req);
if ( req.params.token ) {
//console.log("Request parameters include token " + req.params.token);
var tksplit = req.params.token.split(':');
req.params.token = tksplit[0];
if ( tksplit.length > 1 ) req.params.cache_buster= tksplit[1];
if ( tksplit.length > 1 ) {
req.params.cache_buster= tksplit[1];
}
tksplit = req.params.token.split('@');
if ( tksplit.length > 1 ) {
req.params.signer = tksplit.shift();
if ( ! req.params.signer ) req.params.signer = user;
else if ( req.params.signer != user ) {
var err = new Error('Cannot use map signature of user "' + req.params.signer + '" on database of user "' + user + '"');
if ( ! req.params.signer ) {
req.params.signer = user;
}
else if ( req.params.signer !== user ) {
var err = new Error('Cannot use map signature of user "' + req.params.signer + '" on database of user "' +
user + '"');
err.http_status = 403;
callback(err);
return;
}
if ( tksplit.length > 1 ) {
var template_hash = tksplit.shift(); // unused
/*var template_hash = */tksplit.shift(); // unused
}
req.params.token = tksplit.shift();
//console.log("Request for token " + req.params.token + " with signature from " + req.params.signer);
@@ -614,20 +606,19 @@ module.exports = function(redisPool) {
// bring all query values onto req.params object
_.extend(req.params, req.query);
// for cartodb, ensure interactivity is cartodb_id or user specified
req.params.interactivity = req.params.interactivity || 'cartodb_id';
if (req.profiler) {
req.profiler.done('req2params.setup');
}
var that = this;
if (req.profiler) req.profiler.done('req2params.setup');
Step(
step(
function getPrivacy(){
me.authorize(req, this);
},
function gatekeep(err, authorized){
if (req.profiler) req.profiler.done('authorize');
if(err) throw err;
if (req.profiler) {
req.profiler.done('authorize');
}
assert.ifError(err);
if(!authorized) {
err = new Error("Sorry, you are unauthorized (permission denied)");
err.http_status = 403;
@@ -636,21 +627,12 @@ module.exports = function(redisPool) {
return null;
},
function getDatabase(err){
if(err) throw err;
assert.ifError(err);
pgConnection.setDBConn(user, req.params, this);
},
function getGeometryType(err){
if (req.profiler) req.profiler.done('setDBConn');
if (err) throw err;
if ( ! req.params.table ) return null;
cartoData.getTableGeometryType(req.params.dbname, req.params.table, this);
},
function finishSetup(err, data){
function finishSetup(err) {
if ( err ) { callback(err, req); return; }
if (!_.isNull(data))
_.extend(req.params, {geom_type: data});
// Add default database connection parameters
// if none given
_.defaults(req.params, {
@@ -665,61 +647,5 @@ module.exports = function(redisPool) {
);
};
/**
* Little helper method to get the current list of infowindow variables and return to client
* @param req
* @param callback
*/
me.getInfowindow = function(req, callback){
var that = this;
var user = me.userByReq(req);
Step(
function(){
// TODO: if this step really needed ?
that.req2params(req, this);
},
function getDatabase(err){
if (err) throw err;
cartoData.getUserDBName(user, this);
},
function getInfowindow(err, dbname){
if (err) throw err;
cartoData.getTableInfowindow(dbname, req.params.table, this);
},
function(err, data){
callback(err, data);
}
);
};
/**
* Little helper method to get map metadata and return to client
* @param req
* @param callback
*/
me.getMapMetadata = function(req, callback){
var that = this;
var user = me.userByReq(req);
Step(
function(){
// TODO: if this step really needed ?
that.req2params(req, this);
},
function getDatabase(err){
if (err) throw err;
cartoData.getUserDBName(user, this);
},
function getMapMetadata(err, dbname){
if (err) throw err;
cartoData.getTableMapMetadata(dbname, req.params.table, this);
},
function(err, data){
callback(err, data);
}
);
};
return me;
};

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

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

1255
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "1.28.2",
"version": "2.8.0",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
@@ -22,28 +22,31 @@
"Sandro Santilli <strk@vizzuality.com>"
],
"dependencies": {
"node-varnish": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0",
"underscore" : "~1.6.0",
"dot": "~1.0.2",
"windshaft": "~0.37.2",
"windshaft": "0.48.0",
"step": "~0.0.5",
"queue-async": "~1.0.7",
"request": "~2.9.203",
"cartodb-redis": "~0.11.0",
"cartodb-redis": "~0.13.0",
"cartodb-psql": "~0.4.0",
"redis-mpool": "~0.3.0",
"fastly-purge": "~1.0.0",
"redis-mpool": "~0.4.0",
"lzma": "~1.3.7",
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
"rollbar": "~0.3.13"
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb"
},
"devDependencies": {
"istanbul": "~0.3.6",
"mocha": "~1.21.4",
"nock": "~1.3.0",
"jshint": "~2.6.0",
"redis": "~0.8.6",
"strftime": "~0.8.2",
"semver": "~1.1.4"
},
"scripts": {
"test": "make check"
"preinstall": "make pre-install",
"test": "make test-all"
},
"engines": {
"node": ">=0.8 <0.11",

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
@@ -69,6 +70,10 @@ while [ -n "$1" ]; do
OPT_CREATE_REDIS=no
shift
continue
elif test "$1" = "--with-coverage"; then
OPT_COVERAGE=yes
shift
continue
# This is kept for backward compatibility
elif test "$1" = "--nocreate"; then
OPT_CREATE_REDIS=no
@@ -85,6 +90,7 @@ if [ -z "$1" ]; then
echo "Options:" >&2
echo " --nocreate do not create the test environment on start" >&2
echo " --nodrop do not drop the test environment on exit" >&2
echo " --with-coverage use istanbul to determine code coverage" >&2
exit 1
fi
@@ -112,8 +118,13 @@ cd -
PATH=node_modules/.bin/:$PATH
echo "Running tests"
mocha -t 10000 -u tdd ${MOCHA_OPTS} ${TESTS}
if test x"$OPT_COVERAGE" = xyes; then
echo "Running tests with coverage"
./node_modules/.bin/istanbul cover node_modules/.bin/_mocha -- -u tdd -t 5000 ${TESTS}
else
echo "Running tests"
mocha -u tdd -t 5000 ${TESTS}
fi
ret=$?
cleanup

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

@@ -1,32 +1,41 @@
require(__dirname + '/../../support/test_helper');
var assert = require('../../support/assert');
var redis = require('redis');
var Step = require('step');
var helper = require(__dirname + '/../../support/test_helper');
var SqlApiEmulator = require(__dirname + '/../../support/SQLAPIEmu.js');
var step = require('step');
var FastlyPurge = require('fastly-purge');
var NamedMapsCacheEntry = require(__dirname + '/../../../lib/cartodb/cache/model/named_maps_entry');
var SurrogateKeysCache = require(__dirname + '/../../../lib/cartodb/cache/surrogate_keys_cache');
var CartodbWindshaft = require(__dirname + '/../../../lib/cartodb/cartodb_windshaft');
var ServerOptions = require(__dirname + '/../../../lib/cartodb/server_options');
var serverOptions = ServerOptions();
suite('templates surrogate keys', function() {
describe('templates surrogate keys', function() {
var sqlApiServer;
var redisClient = redis.createClient(global.environment.redis.port);
// Enable Varnish purge for tests
serverOptions.varnish_purge_enabled = true;
var varnishHost = global.environment.varnish.host;
global.environment.varnish.host = '127.0.0.1';
var varnishPurgeEnabled = global.environment.varnish.purge_enabled;
global.environment.varnish.purge_enabled = true;
var fastlyConfig = global.environment.fastly;
var FAKE_FASTLY_API_KEY = 'fastly-api-key';
var FAKE_FASTLY_SERVICE_ID = 'fake-service-id';
global.environment.fastly = {
enabled: true,
// the fastly api key
apiKey: FAKE_FASTLY_API_KEY,
// the service that will get surrogate key invalidation
serviceId: FAKE_FASTLY_SERVICE_ID
};
var serverOptions = require('../../../lib/cartodb/server_options')();
var server = new CartodbWindshaft(serverOptions);
var templateOwner = 'localhost',
templateName = 'acceptance',
expectedTemplateId = templateOwner + '@' + templateName,
expectedTemplateId = templateName,
template = {
version: '0.0.1',
name: templateName,
@@ -48,15 +57,31 @@ suite('templates surrogate keys', function() {
},
expectedBody = { template_id: expectedTemplateId };
suiteSetup(function(done) {
sqlApiServer = new SqlApiEmulator(global.environment.sqlapi.port, done);
});
var varnishHttpUrl = [
'http://', global.environment.varnish.host, ':', global.environment.varnish.http_port
].join('');
var surrogateKeysCacheInvalidateFn = SurrogateKeysCache.prototype.invalidate;
var cacheEntryKey = new NamedMapsCacheEntry(templateOwner, templateName).key();
var invalidationMatchHeader = '\\b' + cacheEntryKey + '\\b';
var fastlyPurgePath = '/service/' + FAKE_FASTLY_SERVICE_ID + '/purge/' + encodeURIComponent(cacheEntryKey);
var nock = require('nock');
nock.enableNetConnect(/(127.0.0.1:5555|cartocdn.com)/);
after(function(done) {
serverOptions.varnish_purge_enabled = false;
global.environment.varnish.host = varnishHost;
global.environment.varnish.purge_enabled = varnishPurgeEnabled;
global.environment.fastly = fastlyConfig;
nock.restore();
done();
});
function createTemplate(callback) {
var postTemplateRequest = {
url: '/tiles/template?api_key=1234',
url: '/api/v1/map/named?api_key=1234',
method: 'POST',
headers: {
host: templateOwner,
@@ -65,7 +90,7 @@ suite('templates surrogate keys', function() {
data: JSON.stringify(template)
};
Step(
step(
function postTemplate() {
var next = this;
assert.response(server,
@@ -92,15 +117,23 @@ suite('templates surrogate keys', function() {
);
}
test("update template calls surrogate keys invalidation", function(done) {
var cacheEntryKey;
var surrogateKeysCacheInvalidateMethodInvoked = false;
SurrogateKeysCache.prototype.invalidate = function(cacheEntry) {
cacheEntryKey = cacheEntry.key();
surrogateKeysCacheInvalidateMethodInvoked = true;
};
it("invalidates surrogate keys on template update", function(done) {
Step(
var scope = nock(varnishHttpUrl)
.intercept('/key', 'PURGE')
.matchHeader('Invalidation-Match', invalidationMatchHeader)
.reply(204, '');
var fastlyScope = nock(FastlyPurge.FASTLY_API_ENDPOINT)
.post(fastlyPurgePath)
.matchHeader('Fastly-Key', FAKE_FASTLY_API_KEY)
.matchHeader('Fastly-Soft-Purge', 1)
.matchHeader('Accept', 'application/json')
.reply(200, {
status:'ok'
});
step(
function createTemplateToUpdate() {
createTemplate(this);
},
@@ -109,7 +142,7 @@ suite('templates surrogate keys', function() {
throw err;
}
var updateTemplateRequest = {
url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234',
url: '/api/v1/map/named/' + expectedTemplateId + '/?api_key=1234',
method: 'PUT',
headers: {
host: templateOwner,
@@ -135,8 +168,8 @@ suite('templates surrogate keys', function() {
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, expectedBody);
assert.ok(surrogateKeysCacheInvalidateMethodInvoked);
assert.equal(cacheEntryKey, new NamedMapsCacheEntry(templateOwner, templateName).key());
assert.equal(scope.pendingMocks().length, 0);
assert.equal(fastlyScope.pendingMocks().length, 0);
return null;
},
@@ -156,16 +189,23 @@ suite('templates surrogate keys', function() {
);
});
test("delete template calls surrogate keys invalidation", function(done) {
it("invalidates surrogate on template deletion", function(done) {
var cacheEntryKey;
var surrogateKeysCacheInvalidateMethodInvoked = false;
SurrogateKeysCache.prototype.invalidate = function(cacheEntry) {
cacheEntryKey = cacheEntry.key();
surrogateKeysCacheInvalidateMethodInvoked = true;
};
var scope = nock(varnishHttpUrl)
.intercept('/key', 'PURGE')
.matchHeader('Invalidation-Match', invalidationMatchHeader)
.reply(204, '');
Step(
var fastlyScope = nock(FastlyPurge.FASTLY_API_ENDPOINT)
.post(fastlyPurgePath)
.matchHeader('Fastly-Key', FAKE_FASTLY_API_KEY)
.matchHeader('Fastly-Soft-Purge', 1)
.matchHeader('Accept', 'application/json')
.reply(200, {
status:'ok'
});
step(
function createTemplateToDelete() {
createTemplate(this);
},
@@ -174,7 +214,7 @@ suite('templates surrogate keys', function() {
throw err;
}
var deleteTemplateRequest = {
url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234',
url: '/api/v1/map/named/' + expectedTemplateId + '/?api_key=1234',
method: 'DELETE',
headers: {
host: templateOwner,
@@ -197,8 +237,8 @@ suite('templates surrogate keys', function() {
throw err;
}
assert.ok(surrogateKeysCacheInvalidateMethodInvoked);
assert.equal(cacheEntryKey, new NamedMapsCacheEntry(templateOwner, templateName).key());
assert.equal(scope.pendingMocks().length, 0);
assert.equal(fastlyScope.pendingMocks().length, 0);
return null;
},
@@ -208,11 +248,66 @@ suite('templates surrogate keys', function() {
);
});
suiteTeardown(function(done) {
SurrogateKeysCache.prototype.invalidate = surrogateKeysCacheInvalidateFn;
// Enable Varnish purge for tests
serverOptions.varnish_purge_enabled = false;
sqlApiServer.close(done);
it("should update template even if surrogate key invalidation fails", function(done) {
var scope = nock(varnishHttpUrl)
.intercept('/key', 'PURGE')
.matchHeader('Invalidation-Match', invalidationMatchHeader)
.reply(503, '');
step(
function createTemplateToUpdate() {
createTemplate(this);
},
function putValidTemplate(err) {
if (err) {
throw err;
}
var updateTemplateRequest = {
url: '/api/v1/map/named/' + expectedTemplateId + '/?api_key=1234',
method: 'PUT',
headers: {
host: templateOwner,
'Content-Type': 'application/json'
},
data: JSON.stringify(template)
};
var next = this;
assert.response(server,
updateTemplateRequest,
{
status: 200
},
function(res) {
next(null, res);
}
);
},
function checkValidUpdate(err, res) {
if (err) {
throw err;
}
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, expectedBody);
assert.equal(scope.pendingMocks().length, 0);
return null;
},
function finish(err) {
if ( err ) {
return done(err);
}
redisClient.keys("map_*|localhost", function(err, keys) {
if ( err ) {
return done(err);
}
redisClient.del(keys, function(err) {
return done(err);
});
});
}
);
});
});

View File

@@ -1,21 +0,0 @@
var assert = require('../support/assert');
require(__dirname + '/../support/test_helper');
var CacheValidator = require(__dirname + '/../../lib/cartodb/cache_validator');
var VarnishEmu = require('../support/VarnishEmu');
suite('cache_validator', function() {
test('should call purge on varnish when invalidate database', function(done) {
var varnish = new VarnishEmu(function(cmds) {
assert.ok(cmds.length == 1);
assert.equal('purge obj.http.X-Cache-Channel ~ \"^test_db:(.*test_cache.*)|(table)$\"\n', cmds[0].toString('utf8'));
done();
},
function() {
CacheValidator.init('localhost', 1337);
CacheValidator.invalidate_db('test_db', 'test_cache');
});
});
});

View File

@@ -1,11 +1,16 @@
var helper = require(__dirname + '/../support/test_helper');
require(__dirname + '/../support/test_helper');
var assert = require('../support/assert');
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')();
var server = new CartodbWindshaft(serverOptions);
suite('health checks', function () {
var metadataBackend = {};
var tilelive = {};
var HealthCheck = require('../../lib/cartodb/monitoring/health_check');
var healthCheck = new HealthCheck(metadataBackend, tilelive);
describe('health checks', function () {
function resetHealthConfig() {
global.environment.health = {
@@ -25,7 +30,7 @@ suite('health checks', function () {
}
};
test('returns 200 and ok=true with enabled configuration', function (done) {
it('returns 200 and ok=true with enabled configuration', function (done) {
resetHealthConfig();
assert.response(server,
@@ -46,29 +51,44 @@ suite('health checks', function () {
);
});
test('fails for invalid user because it is not in redis', function (done) {
resetHealthConfig();
it('error if disabled file exists', function(done) {
var fs = require('fs');
global.environment.health.username = 'invalid';
var readFileFn = fs.readFile;
fs.readFile = function(filename, callback) {
callback(null, "Maintenance");
};
healthCheck.check(null, function(err/*, result*/) {
assert.equal(err.message, "Maintenance");
assert.equal(err.http_status, 503);
done();
fs.readFile = readFileFn;
});
});
assert.response(server,
healthCheckRequest,
{
status: 503
},
function (res, err) {
assert.ok(!err);
it('not err if disabled file does not exists', function(done) {
resetHealthConfig();
var parsed = JSON.parse(res.body);
global.environment.disabled_file = '/tmp/ftreftrgtrccre';
assert.equal(parsed.enabled, true);
assert.equal(parsed.ok, false);
assert.response(server,
healthCheckRequest,
{
status: 200
},
function (res, err) {
assert.ok(!err);
assert.equal(parsed.result.redis.ok, false);
var parsed = JSON.parse(res.body);
done();
}
);
});
assert.equal(parsed.enabled, true);
assert.equal(parsed.ok, true);
done();
}
);
});
});

307
test/acceptance/limits.js Normal file
View File

@@ -0,0 +1,307 @@
require('../support/test_helper');
var assert = require('../support/assert');
var _ = require('underscore');
var redis = require('redis');
var CartodbWindshaft = require('../../lib/cartodb/cartodb_windshaft');
var serverOptions = require('../../lib/cartodb/server_options');
describe('render limits', function() {
var layergroupUrl = '/api/v1/map';
var redisClient = redis.createClient(global.environment.redis.port);
after(function(done) {
redisClient.keys("map_style|*", function(err, matches) {
redisClient.del(matches, function() {
done();
});
});
});
var server;
beforeEach(function() {
server = new CartodbWindshaft(serverOptions());
server.setMaxListeners(0);
});
var keysToDelete = [];
afterEach(function(done) {
redisClient.DEL(keysToDelete, function() {
keysToDelete = [];
done();
});
});
var user = 'localhost';
var pointSleepSql = "SELECT pg_sleep(0.5)," +
" 'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator, 1 cartodb_id";
var pointCartoCss = '#layer { marker-fill:red; }';
var polygonSleepSql = "SELECT pg_sleep(0.5)," +
" ST_Buffer('SRID=3857;POINT(0 0)'::geometry, 100000000) the_geom_webmercator, 1 cartodb_id";
var polygonCartoCss = '#layer { polygon-fill:red; }';
function singleLayergroupConfig(sql, cartocss) {
return {
version: '1.0.0',
layers: [
{
type: 'mapnik',
options: {
sql: sql,
cartocss: cartocss,
cartocss_version: '2.0.1'
}
}
]
};
}
function createRequest(layergroup, userHost) {
return {
url: layergroupUrl,
method: 'POST',
headers: {
host: userHost,
'Content-Type': 'application/json'
},
data: JSON.stringify(layergroup)
};
}
function withRenderLimit(user, renderLimit, callback) {
redisClient.SELECT(5, function(err) {
if (err) {
return callback(err);
}
var userLimitsKey = 'limits:tiler:' + user;
redisClient.HSET(userLimitsKey, 'render', renderLimit, function(err) {
if (err) {
return callback(err);
}
keysToDelete.push(userLimitsKey);
return callback();
});
});
}
describe('with onTileErrorStrategy DISABLED', function() {
var onTileErrorStrategyEnabled;
before(function() {
onTileErrorStrategyEnabled = global.environment.enabledFeatures.onTileErrorStrategy;
global.environment.enabledFeatures.onTileErrorStrategy = false;
});
after(function() {
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategyEnabled;
});
it("layergroup creation fails if test tile is slow", function(done) {
withRenderLimit(user, 50, function(err) {
if (err) {
return done(err);
}
var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss);
assert.response(server,
createRequest(layergroup, user),
{
status: 400
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { errors: [ 'Render timed out' ] });
done();
}
);
});
});
it("layergroup creation does not fail if user limit is high enough even if test tile is slow", function(done) {
withRenderLimit(user, 5000, function(err) {
if (err) {
return done(err);
}
var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss);
assert.response(server,
createRequest(layergroup, user),
{
status: 200
},
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
done();
}
);
});
});
it("layergroup creation works if test tile is fast but tile request fails if they are slow", function(done) {
withRenderLimit(user, 50, function(err) {
if (err) {
return done(err);
}
var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss);
assert.response(server,
createRequest(layergroup, user),
{
status: 200
},
function(res) {
assert.response(server,
{
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
layergroupId: JSON.parse(res.body).layergroupid,
z: 0,
x: 0,
y: 0
}),
method: 'GET',
headers: {
host: 'localhost'
},
encoding: 'binary'
},
{
status: 400
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { errors: ['Render timed out'] });
done();
}
);
}
);
});
});
it("tile request does not fail if user limit is high enough", function(done) {
withRenderLimit(user, 5000, function(err) {
if (err) {
return done(err);
}
var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss);
assert.response(server,
createRequest(layergroup, user),
{
status: 200
},
function(res) {
assert.response(server,
{
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
layergroupId: JSON.parse(res.body).layergroupid,
z: 0,
x: 0,
y: 0
}),
method: 'GET',
headers: {
host: 'localhost'
},
encoding: 'binary'
},
{
status: 200,
headers: {
'Content-Type': 'image/png'
}
},
function(res, err) {
done(err);
}
);
}
);
});
});
});
describe('with onTileErrorStrategy', function() {
it("layergroup creation works even if test tile is slow", function(done) {
withRenderLimit(user, 50, function(err) {
if (err) {
return done(err);
}
var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss);
assert.response(server,
createRequest(layergroup, user),
{
status: 200
},
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
done();
}
);
});
});
it("layergroup creation and tile requests works even if they are slow but returns fallback", function(done) {
withRenderLimit(user, 50, function(err) {
if (err) {
return done(err);
}
var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss);
assert.response(server,
createRequest(layergroup, user),
{
status: 200
},
function(res) {
assert.response(server,
{
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
layergroupId: JSON.parse(res.body).layergroupid,
z: 0,
x: 0,
y: 0
}),
method: 'GET',
headers: {
host: 'localhost'
},
encoding: 'binary'
},
{
status: 200,
headers: {
'Content-Type': 'image/png'
}
},
function(res, err) {
if (err) {
done(err);
}
assert.imageEqualsFile(res.body, './test/fixtures/render-timeout-fallback.png', 25,
function(imgErr/*, similarity*/) {
done(imgErr);
}
);
}
);
}
);
});
});
});
});

View File

@@ -1,12 +1,8 @@
var assert = require('../support/assert');
var tests = module.exports = {};
var _ = require('underscore');
var redis = require('redis');
var querystring = require('querystring');
var semver = require('semver');
var Step = require('step');
var step = require('step');
var strftime = require('strftime');
var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js');
var redis_stats_db = 5;
var helper = require(__dirname + '/../support/test_helper');
@@ -17,42 +13,39 @@ var IMAGE_EQUALS_TOLERANCE_PER_MIL = 20;
var IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL = 25;
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
var ServerOptions = require(__dirname + '/../../lib/cartodb/server_options');
serverOptions = ServerOptions();
var server = new CartodbWindshaft(serverOptions);
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions());
server.setMaxListeners(0);
[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) {
['/api/v1/map', '/user/localhost/api/v1/map'].forEach(function(layergroup_url) {
suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() {
var suiteName = 'multilayer:postgres=layergroup_url=' + layergroup_url;
suite(suiteName, function() {
var cdbQueryTablesFromPostgresEnabledValue = true;
var redis_client = redis.createClient(global.environment.redis.port);
var sqlapi_server;
var expected_last_updated_epoch = 1234567890123; // this is hard-coded into SQLAPIEmu
var expected_last_updated = new Date(expected_last_updated_epoch).toISOString();
var test_user = _.template(global.environment.postgres_auth_user, {user_id:1});
var test_pubuser = global.environment.postgres.user;
var test_database = test_user + '_db';
suiteSetup(function(done){
global.environment.enabledFeatures = { cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue };
sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done);
});
test("layergroup with 2 layers, each with its style", function(done) {
var layergroup = {
version: '1.0.0',
layers: [
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2',
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' +
' from test_table limit 2',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.0.1',
interactivity: 'cartodb_id'
} },
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, -5e6, 0) as the_geom_webmercator from test_table limit 2 offset 2',
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, -5e6, 0) as the_geom_webmercator' +
' from test_table limit 2 offset 2',
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
interactivity: 'cartodb_id'
@@ -61,29 +54,22 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
};
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
Step(
step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
var expectedBody = { layergroupid: expected_token };
// check last modified
var qTables = JSON.stringify({
'q': 'SELECT CDB_QueryTables($windshaft$'
+ layergroup.layers[0].options.sql + ';'
+ layergroup.layers[1].options.sql
+ '$windshaft$)'
});
assert.equal(parsedBody.last_updated, expected_last_updated);
if ( expected_token ) {
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
}
else expected_token = parsedBody.layergroupid.split(':')[0];
next(null, res);
@@ -94,7 +80,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png',
url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
@@ -114,19 +100,23 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
var jsonquery = cc.substring(dbname.length + 1);
var sentquery = JSON.parse(jsonquery);
var expectedQuery = [layergroup.layers[0].options.sql, ';', layergroup.layers[1].options.sql].join('');
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
+ expectedQuery
+ '$windshaft$) as tablenames )'
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
+ ' FROM CDB_TableMetadata m'
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
var expectedQuery = [
layergroup.layers[0].options.sql, ';',
layergroup.layers[1].options.sql
].join('');
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' +
expectedQuery +
'$windshaft$) as tablenames )' +
' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' +
' FROM CDB_TableMetadata m' +
' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
}
assert.imageEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.png', IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL,
function(err, similarity) {
next(err);
});
assert.imageEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.png',
IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL, function(err/*, similarity*/) {
next(err);
}
);
});
},
// See https://github.com/CartoDB/Windshaft-cartodb/issues/170
@@ -135,14 +125,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/localhost@' + expected_token + ':cb0/0/0/0.png',
url: layergroup_url + '/localhost@' + expected_token + ':cb0/0/0/0.png',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
}, {}, function(res) {
assert.equal(res.statusCode, 403, res.statusCode + ':' + res.body);
var parsed = JSON.parse(res.body);
var msg = parsed.error; // TODO: should it be "errors" ?
var msg = parsed.errors[0];
assert.ok(msg.match(/permission denied/i), msg);
next(err);
});
@@ -152,15 +142,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token
+ '/0/0/0/0.grid.json',
url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json',
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
assert.utfgridEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.layer0.grid.json', 2,
function(err, similarity) {
function(err/*, similarity*/) {
next(err);
});
});
@@ -170,15 +159,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token
+ '/1/0/0/0.grid.json',
url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json',
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
assert.utfgridEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.layer1.grid.json', 2,
function(err, similarity) {
function(err/*, similarity*/) {
next(err);
});
});
@@ -204,25 +192,25 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
test("should include serverMedata in the response", function(done) {
global.environment.serverMetadata = { cdn_url : { http:'test', https: 'tests' } }
global.environment.serverMetadata = { cdn_url : { http:'test', https: 'tests' } };
var layergroup = {
version: '1.0.0',
layers: [
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2',
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' +
' from test_table limit 2',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.0.1'
} }
]
};
var expected_token;
Step(
step(
function do_create_get()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
}, {}, function(res, err) { next(err, res); });
@@ -232,7 +220,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
assert.ok(_.isEqual(parsed.cdn_url, global.environment.serverMetadata.cdn_url));
done();
}
)
);
});
@@ -242,7 +230,8 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
version: '1.0.0',
layers: [
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2',
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' +
' from test_table limit 2',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.0.1'
} }
@@ -250,12 +239,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
};
var expected_token;
Step(
step(
function do_create_get()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
}, {}, function(res, err) { next(err, res); });
@@ -299,7 +288,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
]
};
assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
}, {}, function(res) {
@@ -314,14 +303,15 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
version: '1.0.0',
layers: [
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2',
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' +
' from test_table limit 2',
cartocss: '#layer { invalid-rule:red; }',
cartocss_version: '2.0.1'
} }
]
};
assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {host: 'localhost'}
}, {}, function(res) {
@@ -337,8 +327,8 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
version: '1.0.0',
layers: [
{ options: {
sql: 'select 1 as cartodb_id, '
+ 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator from test_table limit 1',
sql: 'select 1 as cartodb_id, ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!))' +
' as the_geom_webmercator from test_table limit 1',
cartocss: '#layer { polygon-fill:red; }',
cartocss_version: '2.0.1',
interactivity: 'cartodb_id'
@@ -347,25 +337,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
};
var expected_token; // = "6d8e4ad5458e2d25cf0eef38e38717a6";
Step(
step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
var expectedBody = { layergroupid: expected_token };
// check last modified
var qTables = JSON.stringify({
'q': 'SELECT CDB_QueryTables($windshaft$'
+ layergroup.layers[0].options.sql
+ '$windshaft$)'
});
assert.equal(parsedBody.last_updated, expected_last_updated);
if ( expected_token ) {
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
@@ -379,7 +362,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb10/1/0/0.png',
url: layergroup_url + "/" + expected_token + ':cb10/1/0/0.png',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
@@ -399,16 +382,16 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
.replace(/!bbox!/g, 'ST_MakeEnvelope(0,0,0,0)')
.replace(/!pixel_width!/g, '1')
.replace(/!pixel_height!/g, '1');
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
+ expectedQuery
+ '$windshaft$) as tablenames )'
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
+ ' FROM CDB_TableMetadata m'
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' +
expectedQuery +
'$windshaft$) as tablenames )' +
' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' +
' FROM CDB_TableMetadata m' +
' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
}
assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL,
function(err, similarity) {
function(err/*, similarity*/) {
next(err);
});
});
@@ -418,7 +401,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb11/4/0/0.png',
url: layergroup_url + "/" + expected_token + ':cb11/4/0/0.png',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
@@ -438,16 +421,16 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
.replace('!bbox!', 'ST_MakeEnvelope(0,0,0,0)')
.replace('!pixel_width!', '1')
.replace('!pixel_height!', '1');
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
+ expectedQuery
+ '$windshaft$) as tablenames )'
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
+ ' FROM CDB_TableMetadata m'
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' +
expectedQuery +
'$windshaft$) as tablenames )' +
' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' +
' FROM CDB_TableMetadata m' +
' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
}
assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL,
function(err, similarity) {
function(err/*, similarity*/) {
next(err);
});
});
@@ -457,15 +440,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token
+ '/0/1/0/0.grid.json',
url: layergroup_url + "/" + expected_token + '/0/1/0/0.grid.json',
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
assert.utfgridEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.grid.json', 2,
function(err, similarity) {
function(err/*, similarity*/) {
next(err);
});
});
@@ -475,15 +457,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token
+ '/0/4/0/0.grid.json',
url: layergroup_url + "/" + expected_token + '/0/4/0/0.grid.json',
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
assert.utfgridEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.grid.json', 2,
function(err, similarity) {
function(err/*, similarity*/) {
next(err);
});
});
@@ -513,8 +494,8 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
version: '1.0.0',
layers: [
{ options: {
sql: 'select 1 as cartodb_id, !pixel_height! as h, '
+ 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
sql: 'select 1 as cartodb_id, !pixel_height! as h,' +
' ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
cartocss: '#layer { polygon-fill:red; }',
cartocss_version: '2.0.1'
} }
@@ -525,7 +506,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
var expected_token; // will be set on first post and checked on second
var now = strftime("%Y%m%d", new Date());
var errors = [];
Step(
step(
function clean_stats()
{
var next = this;
@@ -539,7 +520,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -551,17 +532,16 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
},
function check_global_stats_1(err, val) {
if ( err ) throw err;
assert.equal(val, 1, "Expected score of " + now + " in "
+ statskey + ":global to be 1, got " + val);
assert.equal(val, 1, "Expected score of " + now + " in " + statskey + ":global to be 1, got " + val);
redis_stats_client.zscore(statskey+':stat_tag:random_tag', now, this);
},
function check_tag_stats_1_do_post_2(err, val) {
if ( err ) throw err;
assert.equal(val, 1, "Expected score of " + now + " in "
+ statskey + ":stat_tag:" + layergroup.stat_tag + " to be 1, got " + val);
assert.equal(val, 1, "Expected score of " + now + " in " + statskey + ":stat_tag:" + layergroup.stat_tag +
" to be 1, got " + val);
var next = this;
assert.response(server, {
url: '/tiles/layergroup',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -574,15 +554,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
function check_global_stats_2(err, val)
{
if ( err ) throw err;
assert.equal(val, 2, "Expected score of " + now + " in "
+ statskey + ":global to be 2, got " + val);
assert.equal(val, 2, "Expected score of " + now + " in " + statskey + ":global to be 2, got " + val);
redis_stats_client.zscore(statskey+':stat_tag:' + layergroup.stat_tag, now, this);
},
function check_tag_stats_2(err, val)
{
if ( err ) throw err;
assert.equal(val, 2, "Expected score of " + now + " in "
+ statskey + ":stat_tag:" + layergroup.stat_tag + " to be 2, got " + val);
assert.equal(val, 2, "Expected score of " + now + " in " + statskey + ":stat_tag:" + layergroup.stat_tag +
" to be 2, got " + val);
return 1;
},
function cleanup_map_style(err) {
@@ -612,15 +591,15 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
version: '1.0.0',
layers: [
{ options: {
sql: 'select 1 as cartodb_id, !pixel_height! as h'
+ 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
sql: 'select 1 as cartodb_id, !pixel_height! as h' +
'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
cartocss: '#layer { polygon-fit:red; }',
cartocss_version: '2.0.1'
} }
]
};
assert.response(server, {
url: '/tiles/layergroup',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -648,7 +627,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
]
};
assert.response(server, {
url: '/tiles/layergroup',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -683,26 +662,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
};
var expected_token; // = "b4ed64d93a411a59f330ab3d798e4009";
Step(
step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup?map_key=1234',
url: layergroup_url + '?map_key=1234',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
var expectedBody = { layergroupid: expected_token };
// check last modified
var qTables = JSON.stringify({
'q': 'SELECT CDB_QueryTables($windshaft$'
+ layergroup.layers[0].options.sql + ';'
+ layergroup.layers[1].options.sql
+ '$windshaft$)'
});
assert.equal(parsedBody.last_updated, expected_last_updated);
if ( expected_token ) {
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
@@ -716,7 +687,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png?map_key=1234',
url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?map_key=1234',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
@@ -737,8 +708,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token
+ '/0/0/0/0.grid.json?map_key=1234',
url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json?map_key=1234',
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
@@ -751,8 +721,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token
+ '/1/0/0/0.grid.json?map_key=1234',
url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json?map_key=1234',
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
@@ -766,13 +735,13 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png',
url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
}, {}, function(res) {
assert.equal(res.statusCode, 403);
var re = RegExp('permission denied');
var re = new RegExp('permission denied');
assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body);
next(err);
});
@@ -782,13 +751,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token
+ '/0/0/0/0.grid.json',
url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json',
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
assert.equal(res.statusCode, 403);
var re = RegExp('permission denied');
var re = new RegExp('permission denied');
assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body);
next(err);
});
@@ -798,13 +766,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token
+ '/1/0/0/0.grid.json',
url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json',
headers: {host: 'localhost' },
method: 'GET'
}, {}, function(res) {
assert.equal(res.statusCode, 403);
var re = RegExp('permission denied');
var re = new RegExp('permission denied');
assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body);
next(err);
});
@@ -844,12 +811,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
};
var expected_token; // = "b4ed64d93a411a59f330ab3d798e4009";
Step(
step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup?map_key=1234',
url: layergroup_url + '?map_key=1234',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -859,13 +826,6 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
var expectedBody = { layergroupid: expected_token };
// check last modified
var qTables = JSON.stringify({
'q': 'SELECT CDB_QueryTables($windshaft$'
+ layergroup.layers[0].options.sql
+ '$windshaft$)'
});
assert.equal(parsedBody.last_updated, expected_last_updated);
if ( expected_token ) {
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
@@ -878,7 +838,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png?map_key=1234',
url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?map_key=1234',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
@@ -896,11 +856,10 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
assert.equal(cc.substring(0, dbname.length), dbname);
return null;
},
function do_restart_server(err, res) {
function do_restart_server(err/*, res*/) {
if ( err ) throw err;
// hack simulating restart...
serverOptions = ServerOptions();
server = new CartodbWindshaft(serverOptions);
server = new CartodbWindshaft(serverOptions());
return null;
},
function do_get1(err)
@@ -908,7 +867,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png?map_key=1234',
url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?map_key=1234',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
@@ -954,13 +913,13 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ options: {
sql: "select 1 as cartodb_id, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
cartocss: '#sample { text-name: cartodb_id; text-face-name: "Dejagnu"; }',
cartocss_version: '2.1.0',
cartocss_version: '2.1.0'
} }
]
};
assert.response(server, {
url: '/tiles/layergroup?',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -982,18 +941,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ options: {
sql: "select 'single''quote' as n, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
cartocss: '#s [n="single\'quote" ] { marker-fill:red; }',
cartocss_version: '2.1.0',
cartocss_version: '2.1.0'
} },
{ options: {
sql: "select 'double\"quote' as n, 'SRID=3857;POINT(2 0)'::geometry as the_geom_webmercator",
cartocss: '#s [n="double\\"quote" ] { marker-fill:red; }',
cartocss_version: '2.1.0',
cartocss_version: '2.1.0'
} }
]
};
assert.response(server, {
url: '/tiles/layergroup?',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -1011,12 +970,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ options: {
sql: "select .4 as n, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
cartocss: '#s [n<=.2e-2] { marker-fill:red; }',
cartocss_version: '2.1.0',
cartocss_version: '2.1.0'
} }
]
};
assert.response(server, {
url: '/tiles/layergroup?',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -1039,21 +998,21 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
]
};
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
Step(
step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
var expectedBody = { layergroupid: expected_token };
if ( expected_token ) {
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
}
else {
var token_components = parsedBody.layergroupid.split(':');
@@ -1068,17 +1027,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png',
url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.headers['content-type'], "image/png");
assert.imageEqualsFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png', IMAGE_EQUALS_TOLERANCE_PER_MIL,
function(err, similarity) {
next(err);
});
assert.imageEqualsFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png',
IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err/*, similarity*/) {
next(err);
}
);
});
},
function finish(err) {
@@ -1115,12 +1075,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
]
};
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
Step(
step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup?api_key=1234',
url: layergroup_url + '?api_key=1234',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -1133,6 +1093,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
var parsedBody = JSON.parse(res.body);
if ( expected_token ) {
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
}
else {
var token_components = parsedBody.layergroupid.split(':');
@@ -1146,7 +1107,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err;
var next = this;
assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png?api_key=1234',
url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?api_key=1234',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
@@ -1154,7 +1115,6 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
},
function check_get_tile(err, res) {
if ( err ) throw err;
var next = this;
assert.equal(res.statusCode, 200, res.body);
return null;
},
@@ -1186,10 +1146,10 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
// See https://github.com/CartoDB/Windshaft-cartodb/issues/111
test("sql string can be very long", function(done){
var long_val = 'pretty';
for (var i=0; i<1024; ++i) long_val += ' long'
for (var i=0; i<1024; ++i) long_val += ' long';
long_val += ' string';
var sql = "SELECT ";
for (var i=0; i<16; ++i)
for (i=0; i<16; ++i)
sql += "'" + long_val + "'::text as pretty_long_field_name_" + i + ", ";
sql += "cartodb_id, the_geom_webmercator FROM gadm4 g";
var layergroup = {
@@ -1204,14 +1164,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
};
var errors = [];
var expected_token;
Step(
step(
function do_post()
{
var data = JSON.stringify(layergroup);
assert.ok(data.length > 1024*64);
var next = this;
assert.response(server, {
url: '/tiles/layergroup?api_key=1234',
url: layergroup_url + '?api_key=1234',
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: data
@@ -1223,10 +1183,6 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
var parsedBody = JSON.parse(res.body);
var token_components = parsedBody.layergroupid.split(':');
expected_token = token_components[0];
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
var last_request = sqlapi_server.getLastRequest();
assert.equal(last_request.method, 'POST');
}
return null;
},
function cleanup(err) {
@@ -1257,18 +1213,19 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
version: '1.0.0',
layers: [
{ options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2',
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' +
' from test_table limit 2',
interactivity: 'cartodb_id'
} }
]
};
Step(
step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -1305,12 +1262,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
]
};
Step(
step(
function do_post()
{
var next = this;
assert.response(server, {
url: '/tiles/layergroup',
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
@@ -1334,7 +1291,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
}
var layergroupTtlRequest = {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify({
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify({
version: '1.0.0',
layers: [
{ options: {
@@ -1413,7 +1370,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{
status: 403
},
function(res, err) {
function(res) {
assert.ok(res.body.match(/permission denied for relation test_table_private_1/));
done();
}
@@ -1426,11 +1383,11 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
// This test will add map_style records, like
// 'map_style|null|publicuser|my_table',
redis_client.keys("map_style|*", function(err, matches) {
redis_client.del(matches, function(err) {
redis_client.select(5, function(err, matches) {
redis_client.del(matches, function() {
redis_client.select(5, function() {
redis_client.keys("user:localhost:mapviews*", function(err, matches) {
redis_client.del(matches, function(err) {
sqlapi_server.close(done);
redis_client.del(matches, function() {
done();
});
});
});

View File

@@ -0,0 +1,378 @@
var testHelper = require('../support/test_helper');
var assert = require('../support/assert');
var redis = require('redis');
var _ = require('underscore');
var PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner');
var CartodbWindshaft = require('../../lib/cartodb/cartodb_windshaft');
var serverOptions = require('../../lib/cartodb/server_options')();
var server = new CartodbWindshaft(serverOptions);
server.setMaxListeners(0);
describe('tests from old api translated to multilayer', function() {
var layergroupUrl = '/api/v1/map';
var redisClient = redis.createClient(global.environment.redis.port);
after(function(done) {
// This test will add map_style records, like
// 'map_style|null|publicuser|my_table',
redisClient.keys("map_style|*", function(err, matches) {
redisClient.del(matches, function() {
done();
});
});
});
var wadusSql = 'select 1 as cartodb_id, null::geometry as the_geom_webmercator';
var pointSql = "SELECT 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator, 1::int as cartodb_id";
function singleLayergroupConfig(sql, cartocss) {
return {
version: '1.0.0',
layers: [
{
type: 'mapnik',
options: {
sql: sql,
cartocss: cartocss,
cartocss_version: '2.0.1'
}
}
]
};
}
function createRequest(layergroup, userHost, apiKey) {
var url = layergroupUrl;
if (apiKey) {
url += '?api_key=' + apiKey;
}
return {
url: url,
method: 'POST',
headers: {
host: userHost || 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(layergroup)
};
}
it("layergroup creation fails if CartoCSS is bogus", function(done) {
var layergroup = singleLayergroupConfig(wadusSql, '#my_table3{');
assert.response(server,
createRequest(layergroup),
{
status: 400
},
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.errors[0].match(/^style0/));
assert.ok(parsed.errors[0].match(/missing closing/));
done();
}
);
});
it("multiple bad styles returns 400 with all errors", function(done) {
var layergroup = singleLayergroupConfig(wadusSql, '#my_table4{backgxxxxxround-color:#fff;foo:bar}');
assert.response(server,
createRequest(layergroup),
{
status: 400
},
function(res) {
var parsed = JSON.parse(res.body);
assert.equal(parsed.errors.length, 1);
assert.ok(parsed.errors[0].match(/^style0/));
assert.ok(parsed.errors[0].match(/Unrecognized rule: backgxxxxxround-color/));
assert.ok(parsed.errors[0].match(/Unrecognized rule: foo/));
done();
}
);
});
// Zoom is a special variable
it("Specifying zoom level in CartoCSS does not need a 'zoom' variable in SQL output", function(done) {
var layergroup = singleLayergroupConfig(pointSql, '#gadm4 [ zoom>=3] { marker-fill:red; }');
assert.response(server,
createRequest(layergroup),
{
status: 200
},
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
done();
}
);
});
// See https://github.com/CartoDB/Windshaft-cartodb/issues/88
it("getting a tile from a user-specific database should return an expected tile", function(done) {
var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }');
var backupDBHost = global.environment.postgres.host;
global.environment.postgres.host = '6.6.6.6';
assert.response(server,
createRequest(layergroup, 'cartodb250user'),
{
status: 200
},
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
global.environment.postgres.host = backupDBHost;
done();
}
);
});
// See https://github.com/CartoDB/Windshaft-cartodb/issues/89
it("getting a tile with a user-specific database password", function(done) {
var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }');
var backupDBPass = global.environment.postgres_auth_pass;
global.environment.postgres_auth_pass = '<%= user_password %>';
assert.response(server,
createRequest(layergroup, 'cartodb250user', '4321'),
{
status: 200
},
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
global.environment.postgres_auth_pass = backupDBPass;
done();
}
);
});
it("creating a layergroup from lzma param", function(done){
var params = {
config: JSON.stringify(singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }'))
};
testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function(err, lzma) {
if (err) {
return done(err);
}
assert.response(server,
{
url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma),
method: 'GET',
headers: {
host: 'localhost'
},
encoding: 'binary'
},
{
status: 200
},
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
done();
}
);
});
});
it("creating a layergroup from lzma param, invalid json input", function(done) {
var params = {
config: 'WADUS'
};
testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function(err, lzma) {
if (err) {
return done(err);
}
assert.response(server,
{
url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma),
method: 'GET',
headers: {
host: 'localhost'
},
encoding: 'binary'
},
{
status: 400
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { errors: [ 'Unexpected token W' ] });
done();
}
);
});
});
it("uses queries postgresql to figure affected tables in query", function(done) {
var tableName = 'gadm4';
var expectedCacheChannel = _.template('<%= databaseName %>:public.<%= tableName %>', {
databaseName: _.template(global.environment.postgres_auth_user, {user_id:1}) + '_db',
tableName: tableName
});
var layergroup = singleLayergroupConfig('select * from ' + tableName, '#gadm4 { marker-fill: red; }');
assert.response(server,
{
url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {
host: 'localhost'
}
},
{
status: 200
},
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.ok(res.headers.hasOwnProperty('x-cache-channel'));
assert.equal(res.headers['x-cache-channel'], expectedCacheChannel);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
done();
}
);
});
// https://github.com/CartoDB/cartodb-postgresql/issues/86
it.skip("should not fail with long table names because table name length limit", function(done) {
var tableName = 'long_table_name_with_enough_chars_to_break_querytables_function';
var expectedCacheChannel = _.template('<%= databaseName %>:public.<%= tableName %>', {
databaseName: _.template(global.environment.postgres_auth_user, {user_id:1}) + '_db',
tableName: tableName
});
var layergroup = singleLayergroupConfig('select * from ' + tableName, '#layer { marker-fill: red; }');
assert.response(server,
{
url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {
host: 'localhost'
}
},
{
status: 200
},
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.ok(res.headers.hasOwnProperty('x-cache-channel'));
assert.equal(res.headers['x-cache-channel'], expectedCacheChannel);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
done();
}
);
});
it("creates layergroup fails when postgresql queries fail to figure affected tables in query", function(done) {
var runQueryFn = PgQueryRunner.prototype.run;
PgQueryRunner.prototype.run = function(username, query, queryHandler, callback) {
return queryHandler(new Error('fake error message'), [], callback);
};
var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }');
assert.response(server,
{
url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {
host: 'localhost'
}
},
{
status: 400
},
function(res) {
PgQueryRunner.prototype.run = runQueryFn;
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, {
errors: ["Error: could not fetch affected tables and last updated time: fake error message"]
});
done();
}
);
});
it("tile requests works when postgresql queries fail to figure affected tables in query", function(done) {
var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }');
assert.response(server,
{
url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET',
headers: {
host: 'localhost'
}
},
{
status: 200
},
function(res) {
var runQueryFn = PgQueryRunner.prototype.run;
PgQueryRunner.prototype.run = function(username, query, queryHandler, callback) {
return queryHandler(new Error('failed to query database for affected tables'), [], callback);
};
// reset internal cacheChannel cache
serverOptions.channelCache = {};
assert.response(server,
{
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
layergroupId: JSON.parse(res.body).layergroupid,
z: 0,
x: 0,
y: 0
}),
method: 'GET',
headers: {
host: 'localhost'
},
encoding: 'binary'
},
{
status: 200
},
function(res) {
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
PgQueryRunner.prototype.run = runQueryFn;
done();
}
);
}
);
});
});

View File

@@ -8,12 +8,11 @@ var server = new CartodbWindshaft(serverOptions);
var RedisPool = require('redis-mpool');
var TemplateMaps = require('../../lib/cartodb/template_maps.js');
var Step = require('step');
var _ = require('underscore');
var step = require('step');
suite('named_layers', function() {
describe('named_layers', function() {
// configure redis pool instance to use in tests
var redisPool = RedisPool(global.environment.redis);
var redisPool = new RedisPool(global.environment.redis);
var templateMaps = new TemplateMaps(redisPool, {
max_user_templates: global.environment.maxUserTemplates
@@ -45,6 +44,7 @@ suite('named_layers', function() {
},
layergroup: {
layers: [
wadusLayer,
wadusLayer
]
}
@@ -95,7 +95,7 @@ suite('named_layers', function() {
}
};
suiteSetup(function(done) {
before(function(done) {
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: true};
templateMaps.addTemplate(username, nestedNamedMapTemplate, function(err) {
if (err) {
@@ -112,7 +112,7 @@ suite('named_layers', function() {
});
});
test('should fail for non-existing template name', function(done) {
it('should fail for non-existing template name', function(done) {
var layergroup = {
version: '1.3.0',
layers: [
@@ -125,12 +125,12 @@ suite('named_layers', function() {
]
};
Step(
step(
function createLayergroup() {
var next = this;
assert.response(server,
{
url: '/tiles/layergroup',
url: '/api/v1/map',
method: 'POST',
headers: {
host: 'localhost',
@@ -162,7 +162,7 @@ suite('named_layers', function() {
);
});
test('should return 403 if not properly authorized', function(done) {
it('should return 403 if not properly authorized', function(done) {
var layergroup = {
version: '1.3.0',
@@ -178,12 +178,12 @@ suite('named_layers', function() {
]
};
Step(
step(
function createLayergroup() {
var next = this;
assert.response(server,
{
url: '/tiles/layergroup',
url: '/api/v1/map',
method: 'POST',
headers: {
host: 'localhost',
@@ -219,7 +219,7 @@ suite('named_layers', function() {
});
test('should return 200 and layergroup if properly authorized', function(done) {
it('should return 200 and layergroup if properly authorized', function(done) {
var layergroup = {
version: '1.3.0',
@@ -235,12 +235,12 @@ suite('named_layers', function() {
]
};
Step(
step(
function createLayergroup() {
var next = this;
assert.response(server,
{
url: '/tiles/layergroup',
url: '/api/v1/map',
method: 'POST',
headers: {
host: 'localhost',
@@ -274,7 +274,7 @@ suite('named_layers', function() {
});
test('should return 400 for nested named map layers', function(done) {
it('should return 400 for nested named map layers', function(done) {
var layergroup = {
version: '1.3.0',
@@ -288,12 +288,12 @@ suite('named_layers', function() {
]
};
Step(
step(
function createLayergroup() {
var next = this;
assert.response(server,
{
url: '/tiles/layergroup',
url: '/api/v1/map',
method: 'POST',
headers: {
host: 'localhost',
@@ -326,7 +326,7 @@ suite('named_layers', function() {
});
test('should return 200 and layergroup with private tables', function(done) {
it('should return 200 and layergroup with private tables', function(done) {
var privateTableTemplateName = 'private_table_template';
var privateTableTemplate = {
@@ -361,7 +361,7 @@ suite('named_layers', function() {
]
};
Step(
step(
function createTemplate() {
templateMaps.addTemplate(username, privateTableTemplate, this);
},
@@ -373,7 +373,7 @@ suite('named_layers', function() {
var next = this;
assert.response(server,
{
url: '/tiles/layergroup',
url: '/api/v1/map',
method: 'POST',
headers: {
host: 'localhost',
@@ -408,7 +408,7 @@ suite('named_layers', function() {
var next = this;
assert.response(server,
{
url: '/tiles/layergroup/' + layergroupId + '/0/0/0.png',
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
method: 'GET',
headers: {
host: 'localhost'
@@ -447,7 +447,135 @@ suite('named_layers', function() {
});
test('should return 403 when private table is accessed from non named layer', function(done) {
it('should return 200 and layergroup with private tables and interactivity', function(done) {
var privateTableTemplateNameInteractivity = 'private_table_template_interactivity';
var privateTableTemplate = {
"version": "0.0.1",
"auth": {
"method": "open"
},
"name": privateTableTemplateNameInteractivity,
"layergroup": {
"layers": [
{
"type": "cartodb",
"options": {
"attributes": {
"columns": [
"name"
],
"id": "cartodb_id"
},
"cartocss": "#layer { marker-fill: #cc3300; }",
"cartocss_version": "2.3.0",
"interactivity": "cartodb_id",
"sql": "select * from test_table_private_1"
}
}
]
}
};
var layergroup = {
version: '1.3.0',
layers: [
{
type: 'named',
options: {
name: privateTableTemplateNameInteractivity
}
}
]
};
step(
function createTemplate() {
templateMaps.addTemplate(username, privateTableTemplate, this);
},
function createLayergroup(err) {
if (err) {
throw err;
}
var next = this;
assert.response(server,
{
url: '/api/v1/map',
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(layergroup)
},
{
status: 200
},
function(res, err) {
next(err, res);
}
);
},
function checkLayergroup(err, response) {
if (err) {
throw err;
}
var parsedBody = JSON.parse(response.body);
assert.ok(parsedBody.layergroupid);
assert.ok(parsedBody.last_updated);
return parsedBody.layergroupid;
},
function requestTile(err, layergroupId) {
if (err) {
throw err;
}
var next = this;
assert.response(server,
{
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
method: 'GET',
headers: {
host: 'localhost'
},
encoding: 'binary'
},
{
status: 200,
headers: {
'content-type': 'image/png'
}
},
function(res, err) {
next(err, res);
}
);
},
function handleTileResponse(err, res) {
if (err) {
throw err;
}
test_helper.checkCache(res);
return true;
},
function deleteTemplate(err) {
var next = this;
templateMaps.delTemplate(username, privateTableTemplate, function(/*delErr*/) {
// ignore deletion error
next(err);
});
},
function finish(err) {
done(err);
}
);
});
it('should return 403 when private table is accessed from non named layer', function(done) {
var layergroup = {
version: '1.3.0',
@@ -469,12 +597,12 @@ suite('named_layers', function() {
]
};
Step(
step(
function createLayergroup() {
var next = this;
assert.response(server,
{
url: '/tiles/layergroup',
url: '/api/v1/map',
method: 'POST',
headers: {
host: 'localhost',
@@ -507,8 +635,90 @@ suite('named_layers', function() {
});
it('should return metadata for named layers', function(done) {
suiteTeardown(function(done) {
var layergroup = {
version: '1.3.0',
layers: [
{
type: 'plain',
options: {
color: '#fabada'
}
},
{
type: 'cartodb',
options: {
sql: 'select * from test_table',
cartocss: '#layer { marker-fill: #cc3300; }',
cartocss_version: '2.3.0'
}
},
{
type: 'named',
options: {
name: templateName
}
},
{
type: 'torque',
options: {
sql: "select * from test_table LIMIT 0",
cartocss: "Map { -torque-frame-count:1; -torque-resolution:1; " +
"-torque-aggregation-function:'count(*)'; -torque-time-attribute:'updated_at'; }"
}
}
]
};
step(
function createLayergroup() {
var next = this;
assert.response(server,
{
url: '/api/v1/map',
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(layergroup)
},
{
status: 200
},
function(res, err) {
next(err, res);
}
);
},
function checkLayergroup(err, response) {
if (err) {
throw err;
}
var parsedBody = JSON.parse(response.body);
assert.ok(parsedBody.metadata);
assert.ok(parsedBody.metadata.layers);
assert.equal(parsedBody.metadata.layers.length, 5);
assert.equal(parsedBody.metadata.layers[0].type, 'plain');
assert.equal(parsedBody.metadata.layers[1].type, 'mapnik');
assert.equal(parsedBody.metadata.layers[2].type, 'mapnik');
assert.equal(parsedBody.metadata.layers[3].type, 'mapnik');
assert.equal(parsedBody.metadata.layers[4].type, 'torque');
return null;
},
function finish(err) {
done(err);
}
);
});
after(function(done) {
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: false};
templateMaps.delTemplate(username, nestedNamedMapTemplateName, function(err) {
if (err) {

View File

@@ -0,0 +1,189 @@
var test_helper = require('../support/test_helper');
var RedisPool = require('redis-mpool');
var querystring = require('querystring');
var assert = require('../support/assert');
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')();
var server = new CartodbWindshaft(serverOptions);
var TemplateMaps = require('../../lib/cartodb/template_maps.js');
var NamedMapsCacheEntry = require('../../lib/cartodb/cache/model/named_maps_entry');
describe('named static maps', function() {
// configure redis pool instance to use in tests
var redisPool = new RedisPool(global.environment.redis);
var templateMaps = new TemplateMaps(redisPool, {
max_user_templates: global.environment.maxUserTemplates
});
var wadusLayer = {
type: 'cartodb',
options: {
sql: 'select 1 cartodb_id, null::geometry the_geom_webmercator',
cartocss: '#layer { marker-fill: <%= color %>; }',
cartocss_version: '2.3.0'
}
};
var username = 'localhost';
var templateName = 'valid_template';
var template = {
version: '0.0.1',
name: templateName,
auth: {
method: 'open'
},
"placeholders": {
"color": {
"type": "css_color",
"default": "#cc3300"
}
},
layergroup: {
layers: [
wadusLayer
]
}
};
var tokenAuthTemplateName = 'auth_valid_template';
var tokenAuthTemplate = {
version: '0.0.1',
name: tokenAuthTemplateName,
auth: {
method: 'token',
valid_tokens: ['valid1', 'valid2']
},
placeholders: {
color: {
"type": "css_color",
"default": "#cc3300"
}
},
layergroup: {
layers: [
wadusLayer
]
}
};
var namedMapLayer = {
type: 'named',
options: {
name: templateName,
config: {},
auth_tokens: []
}
};
var nestedNamedMapTemplateName = 'nested_template';
var nestedNamedMapTemplate = {
version: '0.0.1',
name: nestedNamedMapTemplateName,
auth: {
method: 'open'
},
layergroup: {
layers: [
namedMapLayer
]
}
};
before(function (done) {
templateMaps.addTemplate(username, nestedNamedMapTemplate, function (err) {
if (err) {
return done(err);
}
templateMaps.addTemplate(username, tokenAuthTemplate, function (err) {
if (err) {
return done(err);
}
templateMaps.addTemplate(username, template, function (err) {
return done(err);
});
});
});
});
after(function (done) {
templateMaps.delTemplate(username, nestedNamedMapTemplateName, function (err) {
if (err) {
return done(err);
}
templateMaps.delTemplate(username, tokenAuthTemplateName, function (err) {
if (err) {
return done(err);
}
templateMaps.delTemplate(username, templateName, function (err) {
return done(err);
});
});
});
});
function getStaticMap(name, options, callback) {
var url = '/api/v1/map/static/named/' + name + '/640/480.png';
if (options.params) {
url = url + '?' + querystring.stringify(options.params);
}
var requestOptions = {
url: url,
method: 'GET',
headers: {
host: username
}
};
console.log(url);
var statusCode = options.status || 200;
var expectedResponse = {
status: statusCode,
headers: {
'Content-Type': statusCode === 200 ? 'image/png' : 'application/json; charset=utf-8'
}
};
assert.response(server,
requestOptions,
expectedResponse,
function (res, err) {
return callback(err, res);
}
);
}
it('should return a 404 error for nonexistent template name', function (done) {
var nonexistentName = 'nonexistent';
getStaticMap(nonexistentName, { status: 404 }, function(err, res) {
assert.ok(!err);
var parsed = JSON.parse(res.body);
assert.equal(parsed.error, "Template '" + nonexistentName + "' of user '" + username + "' not found");
done();
});
});
it('should return 403 if not properly authorized', function(done) {
getStaticMap(tokenAuthTemplateName, { status: 403 }, function(err, res) {
assert.ok(!err);
var parsed = JSON.parse(res.body);
assert.equal(parsed.error, 'Unauthorized template instantiation');
done();
});
});
it('should return 200 if properly authorized', function(done) {
getStaticMap(tokenAuthTemplateName, { params: { auth_token: 'valid1' } }, function(err, res) {
assert.ok(!err);
test_helper.checkSurrogateKey(res, new NamedMapsCacheEntry(username, tokenAuthTemplateName).key());
done();
});
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
require('../support/test_helper');
var assert = require('../support/assert');
var qs = require('querystring');
var CartodbWindshaft = require('../../lib/cartodb/cartodb_windshaft');
var serverOptions = require('../../lib/cartodb/server_options')();
var server = new CartodbWindshaft(serverOptions);
server.setMaxListeners(0);
describe('get requests x-cache-channel', function() {
var statusOkResponse = {
status: 200
};
var mapConfig = {
version: '1.3.0',
layers: [
{
options: {
sql: 'select * from test_table limit 2',
cartocss: '#layer { marker-fill:red; }',
cartocss_version: '2.3.0',
attributes: {
id:'cartodb_id',
columns: [
'name',
'address'
]
}
}
}
]
};
var layergroupRequest = {
url: '/api/v1/map?config=' + encodeURIComponent(JSON.stringify(mapConfig)),
method: 'GET',
headers: {
host: 'localhost'
}
};
function getRequest(url, addApiKey, callbackName) {
var params = {};
if (!!addApiKey) {
params.api_key = '1234';
}
if (!!callbackName) {
params.callback = callbackName;
}
return {
url: url + '?' + qs.stringify(params),
method: 'GET',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
}
};
}
function validateXCacheChannel(done, expectedCacheChannel) {
return function(res, err) {
if (err) {
return done(err);
}
assert.ok(res.headers['x-cache-channel']);
if (expectedCacheChannel) {
assert.equal(res.headers['x-cache-channel'], expectedCacheChannel);
}
done();
};
}
function noXCacheChannelHeader(done) {
return function(res, err) {
if (err) {
return done(err);
}
assert.ok(
!res.headers['x-cache-channel'],
'did not expect x-cache-channel header, got: `' + res.headers['x-cache-channel'] + '`'
);
done();
};
}
function withLayergroupId(callback) {
assert.response(
server,
layergroupRequest,
statusOkResponse,
function(res, err) {
if (err) {
return callback(err);
}
callback(null, JSON.parse(res.body).layergroupid);
}
);
}
describe('header should be present', function() {
it('/api/v1/map Map instantiation', function(done) {
assert.response(
server,
layergroupRequest,
statusOkResponse,
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
);
});
it ('/api/v1/map/:token/:z/:x/:y@:scale_factor?x.:format Mapnik retina tiles', function(done) {
withLayergroupId(function(err, layergroupId) {
assert.response(
server,
getRequest('/api/v1/map/' + layergroupId + '/0/0/0@2x.png'),
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
);
});
});
it ('/api/v1/map/:token/:z/:x/:y@:scale_factor?x.:format Mapnik tiles', function(done) {
withLayergroupId(function(err, layergroupId) {
assert.response(
server,
getRequest('/api/v1/map/' + layergroupId + '/0/0/0.png'),
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
);
});
});
it ('/api/v1/map/:token/:layer/:z/:x/:y.(:format) Per :layer rendering', function(done) {
withLayergroupId(function(err, layergroupId) {
assert.response(
server,
getRequest('/api/v1/map/' + layergroupId + '/0/0/0/0.png'),
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
);
});
});
it ('/api/v1/map/:token/:layer/attributes/:fid endpoint for info windows', function(done) {
withLayergroupId(function(err, layergroupId) {
assert.response(
server,
getRequest('/api/v1/map/' + layergroupId + '/0/attributes/1'),
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
);
});
});
it ('/api/v1/map/static/center/:token/:z/:lat/:lng/:width/:height.:format static maps', function(done) {
withLayergroupId(function(err, layergroupId) {
assert.response(
server,
getRequest('/api/v1/map/static/center/' + layergroupId + '/0/0/0/400/300.png'),
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
);
});
});
it ('/api/v1/map/static/bbox/:token/:bbox/:width/:height.:format static maps', function(done) {
withLayergroupId(function(err, layergroupId) {
assert.response(
server,
getRequest('/api/v1/map/static/bbox/' + layergroupId + '/-45,-45,45,45/400/300.png'),
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
);
});
});
});
describe('header should NOT be present', function() {
it('/', function(done) {
assert.response(
server,
getRequest('/'),
statusOkResponse,
noXCacheChannelHeader(done)
);
});
it('/version', function(done) {
assert.response(
server,
getRequest('/version'),
statusOkResponse,
noXCacheChannelHeader(done)
);
});
it('/health', function(done) {
assert.response(
server,
getRequest('/health'),
statusOkResponse,
noXCacheChannelHeader(done)
);
});
it('/api/v1/map/named list named maps', function(done) {
assert.response(
server,
getRequest('/api/v1/map/named', true),
statusOkResponse,
noXCacheChannelHeader(done)
);
});
describe('with named maps', function() {
var templateName = 'x_cache';
before(function(done) {
var template = {
version: '0.0.1',
name: templateName,
auth: {
method: 'open'
},
layergroup: mapConfig
};
var namedMapRequest = {
url: '/api/v1/map/named?api_key=1234',
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(template)
};
assert.response(
server,
namedMapRequest,
statusOkResponse,
function(res, err) {
done(err);
}
);
});
after(function(done) {
assert.response(
server,
{
url: '/api/v1/map/named/' + templateName + '?api_key=1234',
method: 'DELETE',
headers: {
host: 'localhost'
}
},
{
status: 204
},
function(res, err) {
done(err);
}
);
});
it('/api/v1/map/named/:template_id Named map retrieval', function(done) {
assert.response(
server,
getRequest('/api/v1/map/named/' + templateName, true),
statusOkResponse,
noXCacheChannelHeader(done)
);
});
it('/api/v1/map/named/:template_id/jsonp Named map retrieval', function(done) {
assert.response(
server,
getRequest('/api/v1/map/named/' + templateName, true, 'cb'),
statusOkResponse,
noXCacheChannelHeader(done)
);
});
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -1,4 +1,4 @@
var test_helper = require('../support/test_helper');
require('../support/test_helper');
var assert = require('assert');
var RedisPool = require('redis-mpool');
@@ -6,11 +6,8 @@ var TemplateMaps = require('../../lib/cartodb/template_maps.js');
var PgConnection = require(__dirname + '/../../lib/cartodb/backends/pg_connection');
var MapConfigNamedLayersAdapter = require('../../lib/cartodb/models/mapconfig_named_layers_adapter');
var Step = require('step');
var _ = require('underscore');
// configure redis pool instance to use in tests
var redisPool = RedisPool(global.environment.redis);
var redisPool = new RedisPool(global.environment.redis);
var pgConnection = new PgConnection(require('cartodb-redis')({ pool: redisPool }));
var templateMaps = new TemplateMaps(redisPool, {
@@ -97,8 +94,8 @@ var multipleLayersTemplate = {
}
};
suite('named_layers datasources', function() {
suiteSetup(function(done) {
describe('named_layers datasources', function() {
before(function(done) {
templateMaps.addTemplate(username, template, function(err) {
if (err) {
return done(err);
@@ -224,7 +221,14 @@ suite('named_layers datasources', function() {
{
desc: 'with a mix of datasource and no datasource depending if layers are named or not',
config: makeNamedMapLayerConfig([simpleNamedLayer, multipleLayersNamedLayer, wadusLayer, simpleNamedLayer, wadusLayer, multipleLayersNamedLayer]),
config: makeNamedMapLayerConfig([
simpleNamedLayer,
multipleLayersNamedLayer,
wadusLayer,
simpleNamedLayer,
wadusLayer,
multipleLayersNamedLayer
]),
test: function(err, layers, datasource, done) {
assert.ok(!err);
@@ -280,14 +284,16 @@ suite('named_layers datasources', function() {
];
testScenarios.forEach(function(testScenario) {
test('should return a list of layers ' + testScenario.desc, function(done) {
mapConfigNamedLayersAdapter.getLayers(username, testScenario.config.layers, pgConnection, function(err, layers, datasource) {
testScenario.test(err, layers, datasource, done);
});
it('should return a list of layers ' + testScenario.desc, function(done) {
mapConfigNamedLayersAdapter.getLayers(username, testScenario.config.layers, pgConnection,
function(err, layers, datasource) {
testScenario.test(err, layers, datasource, done);
}
);
});
});
suiteTeardown(function(done) {
after(function(done) {
templateMaps.delTemplate(username, templateName, function(err) {
if (err) {
return done(err);

View File

@@ -1,4 +1,4 @@
var testHelper = require('../support/test_helper');
require('../support/test_helper');
var assert = require('assert');
var RedisPool = require('redis-mpool');
@@ -6,13 +6,10 @@ var TemplateMaps = require('../../lib/cartodb/template_maps.js');
var PgConnection = require(__dirname + '/../../lib/cartodb/backends/pg_connection');
var MapConfigNamedLayersAdapter = require('../../lib/cartodb/models/mapconfig_named_layers_adapter');
var Step = require('step');
var _ = require('underscore');
suite('mapconfig_named_layers_adapter', function() {
describe('mapconfig_named_layers_adapter', function() {
// configure redis pool instance to use in tests
var redisPool = RedisPool(global.environment.redis);
var redisPool = new RedisPool(global.environment.redis);
var pgConnection = new PgConnection(require('cartodb-redis')({ pool: redisPool }));
var templateMaps = new TemplateMaps(redisPool, {
@@ -138,40 +135,46 @@ suite('mapconfig_named_layers_adapter', function() {
}
suiteSetup(function(done) {
before(function(done) {
templateMaps.addTemplate(username, template, done);
});
test('should fail for named map layer with missing name', function(done) {
it('should fail for named map layer with missing name', function(done) {
var missingNamedMapLayerConfig = makeNamedMapLayerConfig({
config: {}
});
mapConfigNamedLayersAdapter.getLayers(username, missingNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
assert.ok(err);
assert.ok(!layers);
assert.ok(!datasource);
assert.equal(err.message, 'Missing Named Map `name` in layer options');
mapConfigNamedLayersAdapter.getLayers(username, missingNamedMapLayerConfig.layers, pgConnection,
function(err, layers, datasource) {
assert.ok(err);
assert.ok(!layers);
assert.ok(!datasource);
assert.equal(err.message, 'Missing Named Map `name` in layer options');
done();
});
done();
}
);
});
test('should fail for non-existing template name', function(done) {
it('should fail for non-existing template name', function(done) {
var missingTemplateName = 'wadus';
var nonExistentNamedMapLayerConfig = makeNamedMapLayerConfig({
name: missingTemplateName
});
mapConfigNamedLayersAdapter.getLayers(username, nonExistentNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
assert.ok(err);
assert.ok(!layers);
assert.ok(!datasource);
assert.equal(err.message, "Template '" + missingTemplateName + "' of user '" + username + "' not found");
mapConfigNamedLayersAdapter.getLayers(username, nonExistentNamedMapLayerConfig.layers, pgConnection,
function(err, layers, datasource) {
assert.ok(err);
assert.ok(!layers);
assert.ok(!datasource);
assert.equal(
err.message, "Template '" + missingTemplateName + "' of user '" + username + "' not found"
);
done();
});
done();
}
);
});
test('should fail if not properly authorized', function(done) {
it('should fail if not properly authorized', function(done) {
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
if (err) {
return done(err);
@@ -180,18 +183,20 @@ suite('mapconfig_named_layers_adapter', function() {
var nonAuthTokensNamedMapLayerConfig = makeNamedMapLayerConfig({
name: tokenAuthTemplateName
});
mapConfigNamedLayersAdapter.getLayers(username, nonAuthTokensNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
assert.ok(err);
assert.ok(!layers);
assert.ok(!datasource);
assert.equal(err.message, "Unauthorized '" + tokenAuthTemplateName + "' template instantiation");
mapConfigNamedLayersAdapter.getLayers(username, nonAuthTokensNamedMapLayerConfig.layers, pgConnection,
function(err, layers, datasource) {
assert.ok(err);
assert.ok(!layers);
assert.ok(!datasource);
assert.equal(err.message, "Unauthorized '" + tokenAuthTemplateName + "' template instantiation");
templateMaps.delTemplate(username, tokenAuthTemplateName, done);
});
templateMaps.delTemplate(username, tokenAuthTemplateName, done);
}
);
});
});
test('should fail for nested named map layers', function(done) {
it('should fail for nested named map layers', function(done) {
templateMaps.addTemplate(username, nestedNamedMapTemplate, function(err) {
if (err) {
return done(err);
@@ -200,32 +205,36 @@ suite('mapconfig_named_layers_adapter', function() {
var nestedNamedMapLayerConfig = makeNamedMapLayerConfig({
name: nestedNamedMapTemplateName
});
mapConfigNamedLayersAdapter.getLayers(username, nestedNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
assert.ok(err);
assert.ok(!layers);
assert.ok(!datasource);
assert.equal(err.message, 'Nested named layers are not allowed');
mapConfigNamedLayersAdapter.getLayers(username, nestedNamedMapLayerConfig.layers, pgConnection,
function(err, layers, datasource) {
assert.ok(err);
assert.ok(!layers);
assert.ok(!datasource);
assert.equal(err.message, 'Nested named layers are not allowed');
templateMaps.delTemplate(username, nestedNamedMapTemplateName, done);
});
templateMaps.delTemplate(username, nestedNamedMapTemplateName, done);
}
);
});
});
test('should return an expanded list of layers for a named map layer', function(done) {
it('should return an expanded list of layers for a named map layer', function(done) {
var validNamedMapMapLayerConfig = makeNamedMapLayerConfig({
name: templateName
});
mapConfigNamedLayersAdapter.getLayers(username, validNamedMapMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
assert.ok(!err);
assert.ok(layers.length, 1);
assert.ok(layers[0].type, 'cartodb');
assert.notEqual(datasource.getLayerDatasource(0), undefined);
mapConfigNamedLayersAdapter.getLayers(username, validNamedMapMapLayerConfig.layers, pgConnection,
function(err, layers, datasource) {
assert.ok(!err);
assert.ok(layers.length, 1);
assert.ok(layers[0].type, 'cartodb');
assert.notEqual(datasource.getLayerDatasource(0), undefined);
done();
});
done();
}
);
});
test('should return on auth=token with valid tokens provided', function(done) {
it('should return on auth=token with valid tokens provided', function(done) {
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
if (err) {
return done(err);
@@ -235,17 +244,19 @@ suite('mapconfig_named_layers_adapter', function() {
name: tokenAuthTemplateName,
auth_tokens: ['valid1']
});
mapConfigNamedLayersAdapter.getLayers(username, validAuthTokensNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
assert.ok(!err);
assert.equal(layers.length, 1);
assert.notEqual(datasource.getLayerDatasource(0), undefined);
mapConfigNamedLayersAdapter.getLayers(username, validAuthTokensNamedMapLayerConfig.layers, pgConnection,
function(err, layers, datasource) {
assert.ok(!err);
assert.equal(layers.length, 1);
assert.notEqual(datasource.getLayerDatasource(0), undefined);
templateMaps.delTemplate(username, tokenAuthTemplateName, done);
});
templateMaps.delTemplate(username, tokenAuthTemplateName, done);
}
);
});
});
test('should return an expanded list of layers for a named map layer, multiple layers version', function(done) {
it('should return an expanded list of layers for a named map layer, multiple layers version', function(done) {
templateMaps.addTemplate(username, multipleLayersTemplate, function(err) {
if (err) {
return done(err);
@@ -255,24 +266,26 @@ suite('mapconfig_named_layers_adapter', function() {
name: multipleLayersTemplateName,
auth_tokens: ['valid2']
});
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
assert.ok(!err);
assert.equal(layers.length, 2);
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection,
function(err, layers, datasource) {
assert.ok(!err);
assert.equal(layers.length, 2);
assert.equal(layers[0].type, 'mapnik');
assert.equal(layers[0].options.cartocss, '#layer { polygon-fill: green; }');
assert.notEqual(datasource.getLayerDatasource(0), undefined);
assert.equal(layers[0].type, 'mapnik');
assert.equal(layers[0].options.cartocss, '#layer { polygon-fill: green; }');
assert.notEqual(datasource.getLayerDatasource(0), undefined);
assert.equal(layers[1].type, 'cartodb');
assert.equal(layers[1].options.cartocss, '#layer { marker-fill: red; }');
assert.notEqual(datasource.getLayerDatasource(1), undefined);
assert.equal(layers[1].type, 'cartodb');
assert.equal(layers[1].options.cartocss, '#layer { marker-fill: red; }');
assert.notEqual(datasource.getLayerDatasource(1), undefined);
templateMaps.delTemplate(username, multipleLayersTemplateName, done);
});
templateMaps.delTemplate(username, multipleLayersTemplateName, done);
}
);
});
});
test('should replace template params with the given config', function(done) {
it('should replace template params with the given config', function(done) {
templateMaps.addTemplate(username, multipleLayersTemplate, function(err) {
if (err) {
return done(err);
@@ -289,24 +302,26 @@ suite('mapconfig_named_layers_adapter', function() {
},
auth_tokens: ['valid2']
});
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
assert.ok(!err);
assert.equal(layers.length, 2);
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection,
function(err, layers, datasource) {
assert.ok(!err);
assert.equal(layers.length, 2);
assert.equal(layers[0].type, 'mapnik');
assert.equal(layers[0].options.cartocss, '#layer { polygon-fill: ' + polygonColor + '; }');
assert.notEqual(datasource.getLayerDatasource(0), undefined);
assert.equal(layers[0].type, 'mapnik');
assert.equal(layers[0].options.cartocss, '#layer { polygon-fill: ' + polygonColor + '; }');
assert.notEqual(datasource.getLayerDatasource(0), undefined);
assert.equal(layers[1].type, 'cartodb');
assert.equal(layers[1].options.cartocss, '#layer { marker-fill: ' + color + '; }');
assert.notEqual(datasource.getLayerDatasource(1), undefined);
assert.equal(layers[1].type, 'cartodb');
assert.equal(layers[1].options.cartocss, '#layer { marker-fill: ' + color + '; }');
assert.notEqual(datasource.getLayerDatasource(1), undefined);
templateMaps.delTemplate(username, multipleLayersTemplateName, done);
});
templateMaps.delTemplate(username, multipleLayersTemplateName, done);
}
);
});
});
suiteTeardown(function(done) {
after(function(done) {
templateMaps.delTemplate(username, templateName, done);
});
});

View File

@@ -1,81 +0,0 @@
var http = require('http');
var url = require('url');
var _ = require('underscore');
var SQLAPIEmulator = function(port, cb) {
this.queries = [];
var that = this;
this.requests = [];
this.sqlapi_server = http.createServer(function(req,res) {
//console.log("server got request with method " + req.method);
var query;
that.requests.push(req);
if ( req.method == 'GET' ) {
query = url.parse(req.url, true).query;
that.handleQuery(query, res);
}
else if ( req.method == 'POST') {
var data = '';
req.on('data', function(chunk) {
//console.log("GOT Chunk " + chunk);
data += chunk;
});
req.on('end', function() {
//console.log("Data is: "); console.dir(data);
query = JSON.parse(data);
//console.log("handleQuery is " + that.handleQuery);
that.handleQuery(query, res);
});
}
else {
that.handleQuery('SQLAPIEmu does not support method' + req.method, res);
}
}).listen(port, cb);
};
SQLAPIEmulator.prototype.handleQuery = function(query, res) {
this.queries.push(query);
if ( query.q.match('SQLAPIERROR') ) {
res.statusCode = 400;
res.write(JSON.stringify({'error':'Some error occurred'}));
} else if ( query.q.match('SQLAPINOANSWER') ) {
console.log("SQLAPIEmulator will never respond, on request");
return;
} else if (query.q.match('tablenames')) {
var tableNames = JSON.stringify(query);
res.write(queryResult({tablenames: '{' + tableNames + '}', max: 1234567890.123}));
} else if ( query.q.match('EPOCH.* as max') ) {
// This is the structure of the known query sent by tiler
res.write(queryResult({max: 1234567890.123}));
} else {
if ( query.q.match('_private_') && query.api_key === undefined) {
res.statusCode = 403;
res.write(JSON.stringify({'error':'forbidden: ' + JSON.stringify(query)}));
} else {
var qs = JSON.stringify(query);
res.write(queryResult({cdb_querytables: '{' + qs + '}', max: 1234567890.123}));
}
}
res.end();
};
SQLAPIEmulator.prototype.close = function(cb) {
this.sqlapi_server.close(cb);
};
SQLAPIEmulator.prototype.getLastRequest = function() {
return this.requests.pop();
};
function queryResult(row) {
return JSON.stringify({
rows: [row]
});
}
module.exports = SQLAPIEmulator;

View File

@@ -1,25 +0,0 @@
var net = require('net');
module.exports = function(on_cmd_recieved, test_callback) {
var self = this;
var welcome_msg = 'hi, im a varnish emu, right?';
self.commands_recieved = [];
var server = net.createServer(function (socket) {
var command = '';
socket.write("200 " + welcome_msg.length + "\n");
socket.write(welcome_msg);
socket.on('data', function(data) {
self.commands_recieved.push(data);
on_cmd_recieved && on_cmd_recieved(self.commands_recieved);
socket.write('200 0\n');
});
});
server.listen(1337, "127.0.0.1");
server.on('listening', function(){
test_callback();
});
};

View File

@@ -2,12 +2,12 @@
--
-- Requires PostgreSQL 9.x+
--
CREATE OR REPLACE FUNCTION CDB_QueryTables(query text)
RETURNS name[]
CREATE OR REPLACE FUNCTION CDB_QueryTablesText(query text)
RETURNS text[]
AS $$
DECLARE
exp XML;
tables NAME[];
tables text[];
rec RECORD;
rec2 RECORD;
BEGIN
@@ -16,9 +16,9 @@ BEGIN
FOR rec IN SELECT CDB_QueryStatements(query) q LOOP
IF NOT ( rec.q ilike 'select %' or rec.q ilike 'with %' ) THEN
--RAISE WARNING 'Skipping %', rec.q;
CONTINUE;
IF NOT ( rec.q ilike 'select%' or rec.q ilike 'with%' ) THEN
--RAISE WARNING 'Skipping %', rec.q;
CONTINUE;
END IF;
BEGIN
@@ -41,11 +41,11 @@ BEGIN
xpath('//x:Relation-Name/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as x,
xpath('//x:Relation-Name/../x:Schema/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as s
)
SELECT unnest(x)::name as p, unnest(s)::name as sc from inp
SELECT unnest(x) as p, unnest(s) as sc from inp
LOOP
-- RAISE DEBUG 'tab: %', rec2.p;
-- RAISE DEBUG 'sc: %', rec2.sc;
tables := array_append(tables, (rec2.sc || '.' || rec2.p)::name);
tables := array_append(tables, (rec2.sc || '.' || rec2.p));
END LOOP;
-- RAISE DEBUG 'Tables: %', tables;
@@ -65,3 +65,14 @@ BEGIN
return tables;
END
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;
-- Keep CDB_QueryTables with same signature for backwards compatibility.
-- It should probably be removed in the future.
CREATE OR REPLACE FUNCTION CDB_QueryTables(query text)
RETURNS name[]
AS $$
BEGIN
RETURN CDB_QueryTablesText(query)::name[];
END
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;

View File

@@ -189,3 +189,31 @@ INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('test_table_private_
-- GRANT SELECT ON CDB_TableMetadata TO :PUBLICUSER;
GRANT SELECT ON CDB_TableMetadata TO :TESTUSER;
-- long name table
CREATE TABLE
long_table_name_with_enough_chars_to_break_querytables_function
(
updated_at timestamp without time zone DEFAULT now(),
created_at timestamp without time zone DEFAULT now(),
cartodb_id integer NOT NULL,
name character varying,
address character varying,
the_geom geometry,
the_geom_webmercator geometry
);
INSERT INTO long_table_name_with_enough_chars_to_break_querytables_function SELECT * from test_table;
ALTER TABLE ONLY long_table_name_with_enough_chars_to_break_querytables_function
ADD CONSTRAINT long_table_name_with_enough_chars_to_break_querytables_func_pkey PRIMARY KEY (cartodb_id);
CREATE INDEX long_table_name_the_geom_idx
ON long_table_name_with_enough_chars_to_break_querytables_function USING gist (the_geom);
CREATE INDEX long_table_name_the_geom_webmercator_idx
ON long_table_name_with_enough_chars_to_break_querytables_function USING gist (the_geom_webmercator);
GRANT ALL ON TABLE long_table_name_with_enough_chars_to_break_querytables_function TO :TESTUSER;
GRANT SELECT ON TABLE long_table_name_with_enough_chars_to_break_querytables_function TO :PUBLICUSER;
INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('long_table_name_with_enough_chars_to_break_querytables_function'::regclass, '2009-02-13T23:31:30.123Z');

View File

@@ -0,0 +1,60 @@
require('../../support/test_helper');
var assert = require('assert');
var CdbRequest = require('../../../lib/cartodb/models/cdb_request');
describe('req2params', function() {
function createRequest(host, userParam) {
var req = {
params: {},
headers: {
host: host
}
};
if (userParam) {
req.params.user = userParam;
}
return req;
}
it('extracts name from host header', function() {
var cdbRequest = new CdbRequest();
var user = cdbRequest.userByReq(createRequest('localhost'));
assert.equal(user, 'localhost');
});
it('extracts name from subdomain host header in case of no config', function() {
var userFromHostConfig = global.environment.user_from_host;
global.environment.user_from_host = null;
var cdbRequest = new CdbRequest();
var user = cdbRequest.userByReq(createRequest('development.localhost.lan'));
global.environment.user_from_host = userFromHostConfig;
assert.equal(user, 'development');
});
it('considers user param before headers', function() {
var cdbRequest = new CdbRequest();
var user = cdbRequest.userByReq(createRequest('localhost', 'development'));
assert.equal(user, 'development');
});
it('returns undefined when it cannot extract username', function() {
var userFromHostConfig = global.environment.user_from_host;
global.environment.user_from_host = null;
var cdbRequest = new CdbRequest();
var user = cdbRequest.userByReq(createRequest('localhost'));
global.environment.user_from_host = userFromHostConfig;
assert.equal(user, undefined);
});
});

View File

@@ -1,8 +1,6 @@
var assert = require('assert')
, _ = require('underscore')
, redis = require('redis')
, test_helper = require('../../support/test_helper')
, tests = module.exports = {};
var assert = require('assert');
var _ = require('underscore');
var test_helper = require('../../support/test_helper');
suite('req2params', function() {
@@ -24,7 +22,7 @@ suite('req2params', function() {
assert.ok(_.isObject(req.query), 'request has query');
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
assert.ok(req.hasOwnProperty('params'), 'request has params');
assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity');
assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity');
assert.equal(req.params.dbname, test_database, 'could forge dbname: '+ req.params.dbname);
assert.ok(req.params.dbuser === test_pubuser, 'could inject dbuser ('+req.params.dbuser+')');
done();
@@ -38,7 +36,7 @@ suite('req2params', function() {
assert.ok(_.isObject(req.query), 'request has query');
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
assert.ok(req.hasOwnProperty('params'), 'request has params');
assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity');
assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity');
assert.equal(req.params.dbname, test_database);
assert.ok(req.params.dbuser === test_pubuser, 'could inject dbuser ('+req.params.dbuser+')');
done();
@@ -52,7 +50,7 @@ suite('req2params', function() {
assert.ok(_.isObject(req.query), 'request has query');
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
assert.ok(req.hasOwnProperty('params'), 'request has params');
assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity');
assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity');
assert.equal(req.params.dbname, test_database);
assert.equal(req.params.dbuser, test_user);
@@ -65,23 +63,34 @@ suite('req2params', function() {
});
test('it should extend params with decoded lzma', function(done) {
var qo = {
style: 'test',
style_version: '2.1.0',
cache_buster: 5
};
test_helper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) {
opts.req2params({ headers: { host:'localhost' }, query: { non_included: 'toberemoved', api_key: 'test', style: 'override', lzma: data }}, function(err, req) {
if ( err ) { done(err); return; }
var query = req.params
assert.equal(qo.style, query.style)
assert.equal(qo.style_version, query.style_version)
assert.equal(qo.cache_buster, query.cache_buster)
assert.equal('test', query.api_key)
assert.equal(undefined, query.non_included)
done();
var qo = {
config: {
version: '1.3.0'
}
};
test_helper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) {
var req = {
headers: {
host:'localhost'
},
query: {
non_included: 'toberemoved',
api_key: 'test',
style: 'override',
lzma: data
}
};
opts.req2params(req, function(err, req) {
if ( err ) {
return done(err);
}
var query = req.params;
assert.deepEqual(qo.config, query.config);
assert.equal('test', query.api_key);
assert.equal(undefined, query.non_included);
done();
});
});
});
});
});

View File

@@ -1,16 +1,15 @@
var assert = require('assert')
//, _ = require('underscore')
, RedisPool = require('redis-mpool')
, TemplateMaps = require('../../../lib/cartodb/template_maps.js')
, test_helper = require('../../support/test_helper')
, Step = require('step')
, _ = require('underscore')
, tests = module.exports = {};
require('../../support/test_helper');
suite('template_maps', function() {
var assert = require('assert');
var RedisPool = require('redis-mpool');
var TemplateMaps = require('../../../lib/cartodb/template_maps.js');
var step = require('step');
var _ = require('underscore');
describe('template_maps', function() {
// configure redis pool instance to use in tests
var redis_pool = RedisPool(global.environment.redis);
var redis_pool = new RedisPool(global.environment.redis);
var wadusLayer = {
options: {
@@ -20,24 +19,12 @@ suite('template_maps', function() {
}
};
var validTemplate = {
version:'0.0.1',
name: 'first',
auth: {},
layergroup: {
layers: [
wadusLayer
]
}
};
var owner = 'me';
test('does not accept template with unsupported version', function(done) {
it('does not accept template with unsupported version', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'6.6.6',
name:'k', auth: {}, layergroup: {layers:[wadusLayer]} };
Step(
step(
function() {
tmap.addTemplate('me', tpl, this);
},
@@ -52,12 +39,12 @@ suite('template_maps', function() {
);
});
test('does not accept template with missing name', function(done) {
it('does not accept template with missing name', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
auth: {}, layergroup: {layers:[wadusLayer]} };
Step(
step(
function() {
tmap.addTemplate('me', tpl, this);
},
@@ -72,33 +59,52 @@ suite('template_maps', function() {
);
});
test('does not accept template with invalid name', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
auth: {}, layergroup: {layers:[wadusLayer]} };
var invalidnames = [ "ab|", "a b", "a@b", "1ab", "_x", "", " x", "x " ];
var testNext = function() {
if ( ! invalidnames.length ) { done(); return; }
var n = invalidnames.pop();
tpl.name = n;
tmap.addTemplate('me', tpl, function(err) {
if ( ! err ) {
done(new Error("Unexpected success with invalid name '" + n + "'"));
}
else if ( ! err.message.match(/template.*name/i) ) {
done(new Error("Unexpected error message with invalid name '" + n
+ "': " + err));
}
else {
testNext();
}
});
};
testNext();
});
describe('naming', function() {
test('does not accept template with invalid placeholder name', function(done) {
function createTemplate(name) {
return {
version:'0.0.1',
name: name,
auth: {},
layergroup: {
layers: [
wadusLayer
]
}
};
}
var templateMaps = new TemplateMaps(redis_pool);
var invalidNames = [ "ab|", "a b", "a@b", "-1ab", "_x", "", " x", "x " ];
invalidNames.forEach(function(invalidName) {
it('should NOT accept template with invalid name: ' + invalidName, function(done) {
templateMaps.addTemplate('me', createTemplate(invalidName), function(err) {
assert.ok(err, "Unexpected success with invalid name '" + invalidName + "'");
assert.ok(
err.message.match(/template.*name/i),
"Unexpected error message with invalid name '" + invalidName + "': " + err.message
);
done();
});
});
});
var validNames = [
"AB", "1ab", "DFD19A1A-0AC6-11E5-B0CA-6476BA93D4F6", "25ad8300-0ac7-11e5-b93f-6476ba93d4f6"
];
validNames.forEach(function(validName) {
it('should accept template with valid name: ' + validName, function(done) {
templateMaps.addTemplate('me', createTemplate(validName), function(err) {
assert.ok(!err, "Unexpected error with valid name '" + validName + "': " + err);
done();
});
});
});
});
it('does not accept template with invalid placeholder name', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
@@ -115,8 +121,7 @@ suite('template_maps', function() {
done(new Error("Unexpected success with invalid name '" + n + "'"));
}
else if ( ! err.message.match(/invalid.*name/i) ) {
done(new Error("Unexpected error message with invalid name '" + n
+ "': " + err));
done(new Error("Unexpected error message with invalid name '" + n + "': " + err));
}
else {
testNext();
@@ -126,7 +131,7 @@ suite('template_maps', function() {
testNext();
});
test('does not accept template with missing placeholder default', function(done) {
it('does not accept template with missing placeholder default', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
@@ -137,8 +142,7 @@ suite('template_maps', function() {
done(new Error("Unexpected success with missing placeholder default"));
}
else if ( ! err.message.match(/missing default/i) ) {
done(new Error("Unexpected error message with missing placeholder default: "
+ err));
done(new Error("Unexpected error message with missing placeholder default: " + err));
}
else {
done();
@@ -146,7 +150,7 @@ suite('template_maps', function() {
});
});
test('does not accept template with missing placeholder type', function(done) {
it('does not accept template with missing placeholder type', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
@@ -157,8 +161,7 @@ suite('template_maps', function() {
done(new Error("Unexpected success with missing placeholder type"));
}
else if ( ! err.message.match(/missing type/i) ) {
done(new Error("Unexpected error message with missing placeholder default: "
+ err));
done(new Error("Unexpected error message with missing placeholder default: " + err));
}
else {
done();
@@ -167,7 +170,7 @@ suite('template_maps', function() {
});
// See http://github.com/CartoDB/Windshaft-cartodb/issues/128
test('does not accept template with invalid token auth (undefined tokens)',
it('does not accept template with invalid token auth (undefined tokens)',
function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
@@ -179,8 +182,7 @@ suite('template_maps', function() {
done(new Error("Unexpected success with invalid token auth (undefined tokens)"));
}
else if ( ! err.message.match(/invalid 'token' authentication/i) ) {
done(new Error("Unexpected error message with invalid token auth (undefined tokens): "
+ err));
done(new Error("Unexpected error message with invalid token auth (undefined tokens): " + err));
}
else {
done();
@@ -188,14 +190,14 @@ suite('template_maps', function() {
});
});
test('add, get and delete a valid template', function(done) {
it('add, get and delete a valid template', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var expected_failure = false;
var tpl_id;
var tpl = { version:'0.0.1',
name: 'first', auth: {}, layergroup: {layers:[wadusLayer]} };
Step(
step(
function() {
tmap.addTemplate('me', tpl, this);
},
@@ -224,15 +226,14 @@ suite('template_maps', function() {
);
});
test('add multiple templates, list them', function(done) {
it('add multiple templates, list them', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var expected_failure = false;
var tpl1 = { version:'0.0.1', name: 'first', auth: {}, layergroup: {layers:[wadusLayer]} };
var tpl1_id;
var tpl2 = { version:'0.0.1', name: 'second', auth: {}, layergroup: {layers:[wadusLayer]} };
var tpl2_id;
Step(
step(
function addTemplate1() {
tmap.addTemplate('me', tpl1, this);
},
@@ -283,7 +284,7 @@ suite('template_maps', function() {
);
});
test('update templates', function(done) {
it('update templates', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var expected_failure = false;
@@ -294,7 +295,7 @@ suite('template_maps', function() {
layergroup: {layers:[wadusLayer]}
};
var tpl_id;
Step(
step(
function addTemplate() {
tmap.addTemplate(owner, tpl, this);
},
@@ -323,7 +324,6 @@ suite('template_maps', function() {
},
function updateUnexistentTemplate(err) {
if ( err && ! expected_failure) throw err;
expected_failure = false;
assert.ok(err);
assert.ok(err.message.match(/unsupported.*version/i), err);
tpl.version = '0.0.1';
@@ -343,7 +343,7 @@ suite('template_maps', function() {
);
});
test('instanciate templates', function() {
it('instanciate templates', function() {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
@@ -356,7 +356,7 @@ suite('template_maps', function() {
color: { type: "css_color", default: "#a0fF9A" },
name: { type: "sql_literal", default: "test" },
zoom: { type: "number", default: "0" },
test_number: { type: "number", default: 23 },
test_number: { type: "number", default: 23 }
},
layergroup: {
version: '1.0.0',
@@ -400,48 +400,48 @@ suite('template_maps', function() {
// Invalid css_color
var err = null;
try { inst = tmap.instance(tpl1, {color:'##ff00ff'}); }
try { tmap.instance(tpl1, {color:'##ff00ff'}); }
catch (e) { err = e; }
assert.ok(err);
assert.ok(err.message.match(/invalid css_color/i), err);
// Invalid css_color 2 (too few digits)
var err = null;
try { inst = tmap.instance(tpl1, {color:'#ff'}); }
err = null;
try { tmap.instance(tpl1, {color:'#ff'}); }
catch (e) { err = e; }
assert.ok(err);
assert.ok(err.message.match(/invalid css_color/i), err);
// Invalid css_color 3 (too many digits)
var err = null;
try { inst = tmap.instance(tpl1, {color:'#1234567'}); }
err = null;
try { tmap.instance(tpl1, {color:'#1234567'}); }
catch (e) { err = e; }
assert.ok(err);
assert.ok(err.message.match(/invalid css_color/i), err);
// Invalid number
var err = null;
try { inst = tmap.instance(tpl1, {zoom:'#'}); }
err = null;
try { tmap.instance(tpl1, {zoom:'#'}); }
catch (e) { err = e; }
assert.ok(err);
assert.ok(err.message.match(/invalid number/i), err);
// Invalid number 2
var err = null;
try { inst = tmap.instance(tpl1, {zoom:'23e'}); }
err = null;
try { tmap.instance(tpl1, {zoom:'23e'}); }
catch (e) { err = e; }
assert.ok(err);
assert.ok(err.message.match(/invalid number/i), err);
// Valid number
var err = null;
try { inst = tmap.instance(tpl1, {zoom:'-.23e10'}); }
err = null;
try { tmap.instance(tpl1, {zoom:'-.23e10'}); }
catch (e) { err = e; }
assert.ok(!err);
});
// Can set a limit on the number of user templates
test('can limit number of user templates', function(done) {
it('can limit number of user templates', function(done) {
var tmap = new TemplateMaps(redis_pool, {
max_user_templates: 2
});
@@ -450,7 +450,7 @@ suite('template_maps', function() {
var expectErr = false;
var idMe = [];
var idYou = [];
Step(
step(
function oneForMe() {
tpl.name = 'oneForMe';
tmap.addTemplate('me', tpl, this);
@@ -470,7 +470,7 @@ suite('template_maps', function() {
expectErr = true;
tmap.addTemplate('me', tpl, this);
},
function errForMe(err, id) {
function errForMe(err/*, id*/) {
if ( err && ! expectErr ) throw err;
expectErr = false;
assert.ok(err);
@@ -508,7 +508,7 @@ suite('template_maps', function() {
expectErr = true;
tmap.addTemplate('you', tpl, this);
},
function errForYou(err, id) {
function errForYou(err/*, id*/) {
if ( err && ! expectErr ) throw err;
expectErr = false;
assert.ok(err);

View File

@@ -1,11 +1,11 @@
require('../../support/test_helper');
var assert = require('assert');
var RedisPool = require('redis-mpool');
var TemplateMaps = require('../../../lib/cartodb/template_maps');
var test_helper = require('../../support/test_helper');
var Step = require('step');
var tests = module.exports = {};
suite('template_maps_auth', function() {
var TemplateMaps = require('../../../lib/cartodb/template_maps');
describe('template_maps_auth', function() {
// configure redis pool instance to use in tests
var redisPool = new RedisPool(global.environment.redis),
@@ -84,15 +84,15 @@ suite('template_maps_auth', function() {
];
authorizationTestScenarios.forEach(function(testScenario) {
test(testScenario.desc, function(done) {
it(testScenario.desc, function(done) {
var debugMessage = testScenario.expected ? 'should be authorized' : 'unexpectedly authorized';
var result = templateMaps.isAuthorized(testScenario.template, testScenario.token);
assert.equal(result, testScenario.expected, debugMessage);
done();
})
});
});
test("auth as 'open' string is authorized", function(done) {
it("auth as 'open' string is authorized", function(done) {
var template = {
name: 'wadus_template',
auth: 'open'

View File

@@ -1,10 +1,11 @@
require('../../support/test_helper');
var assert = require('assert');
var RedisPool = require('redis-mpool');
var TemplateMaps = require('../../../lib/cartodb/template_maps.js');
var test_helper = require('../../support/test_helper');
var _ = require('underscore');
suite('template_maps', function() {
describe('template_maps', function() {
var redisPool = new RedisPool(global.environment.redis),
templateMaps = new TemplateMaps(redisPool);
@@ -88,7 +89,7 @@ suite('template_maps', function() {
];
testScenarios.forEach(function(testScenario) {
test('adding template returns a new instance with ' + testScenario.desc, function(done) {
it('adding template returns a new instance with ' + testScenario.desc, function(done) {
templateMaps.addTemplate(owner, testScenario.template, function(err, templateId, template) {
assert.ok(!err, 'Unexpected error adding template: ' + (err && err.message));

View File

@@ -1,10 +1,11 @@
require('../../support/test_helper');
var assert = require('assert');
var RedisPool = require('redis-mpool');
var TemplateMaps = require('../../../lib/cartodb/template_maps.js');
var test_helper = require('../../support/test_helper');
var _ = require('underscore');
suite('template_maps', function() {
describe('template_maps', function() {
var redisPool = new RedisPool(global.environment.redis),
templateMaps = new TemplateMaps(redisPool);
@@ -87,7 +88,7 @@ suite('template_maps', function() {
];
testScenarios.forEach(function(testScenario) {
test(testScenario.desc, function(done) {
it(testScenario.desc, function(done) {
templateMaps.addTemplate(owner, testScenario.template, function(err) {

8
tools/README.md Normal file
View File

@@ -0,0 +1,8 @@
Deprecated tools
================
All tools and scripts found in this directory are deprecated and no longer maintained.
Use at your own peril.
In future releases they might get removed.