Compare commits

...

46 Commits
2.3.0 ... 2.5.0

Author SHA1 Message Date
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
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
17 changed files with 503 additions and 213 deletions

34
NEWS.md
View File

@@ -1,5 +1,39 @@
# Changelog
## 2.5.0
Released 2015-06-18
New features:
- Named maps names can start with numbers and can contain dashes (-).
- Adds layergroupid header in map instantiations
Bug fixes:
- Named maps error responses with `{ "errors": ["message"] }` format (#305)
Announcements:
- Upgrades windshaft to [0.45.0](https://github.com/CartoDB/Windshaft/releases/tag/0.45.0)
Enhancements:
- Fix documentation style and error examples
## 2.4.1
Released 2015-06-01
Announcements:
- Upgrades windshaft to [0.44.1](https://github.com/CartoDB/Windshaft/releases/tag/0.44.1)
## 2.4.0
Released 2015-05-26
Announcements:
- Upgrades windshaft to [0.44.0](https://github.com/CartoDB/Windshaft/releases/tag/0.44.0)
## 2.3.0
Released 2015-05-18

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": {
@@ -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; }",
@@ -82,14 +82,23 @@ To get the `URL` to fetch the tiles you need to instantiate the map, where `temp
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": {}
}
]
}
}
```
@@ -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
@@ -173,8 +182,9 @@ The response includes:
- **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.
@@ -189,8 +199,16 @@ curl 'https://documentation.cartodb.com/api/v1/map' -H 'Content-Type: applicatio
<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,19 +216,35 @@ curl 'https://documentation.cartodb.com/api/v1/map' -H 'Content-Type: applicatio
}
```
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
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
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
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
@@ -219,10 +253,44 @@ https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/
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.
@@ -779,14 +891,14 @@ 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.
* Image resolution by default is set to 72 DPI
@@ -803,10 +915,11 @@ 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.

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

View File

@@ -38,6 +38,9 @@ This document list all routes available in Windshaft-cartodb Maps API server.
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
@@ -73,10 +76,10 @@ Something like the following patch should do the trick
```javascript
diff --git a/lib/cartodb/cartodb_windshaft.js b/lib/cartodb/cartodb_windshaft.js
index 477a4c2..f69eebb 100644
index b9429a2..e6cc5f9 100644
--- a/lib/cartodb/cartodb_windshaft.js
+++ b/lib/cartodb/cartodb_windshaft.js
@@ -242,6 +242,20 @@ var CartodbWindshaft = function(serverOptions) {
@@ -212,6 +212,20 @@ var CartodbWindshaft = function(serverOptions) {
}
});

View File

@@ -14,5 +14,3 @@ module.exports = FastlyCacheBackend;
FastlyCacheBackend.prototype.invalidate = function(cacheObject, callback) {
this.fastlyPurge.key(this.serviceId, cacheObject.key(), callback);
};
module.exports = FastlyCacheBackend;

View File

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

View File

@@ -315,23 +315,23 @@ NamedMapsController.prototype.instantiateTemplate = function(req, res, template_
},
function prepareResponse(err, layergroup) {
if ( err ) {
throw 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));
return layergroup;
},
callback
callback(null, layergroup);
}
);
};
NamedMapsController.prototype.finish_instantiation = function(err, response, res) {
if (err) {
var statusCode = 400;
response = { error: ''+err };
response = { errors: [''+err] };
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}
@@ -346,7 +346,7 @@ function finishFn(app, res, description, okResponse) {
var statusCode = 200;
if (err) {
statusCode = 400;
response = { error: '' + err };
response = { errors: ['' + err] };
if ( ! _.isUndefined(err.http_status) ) {
statusCode = err.http_status;
}

View File

@@ -362,17 +362,22 @@ module.exports = function(redisPool) {
var cacheChannel = me.buildCacheChannel(dbName, result.affectedTables);
me.channelCache[cacheKey] = cacheChannel;
if (req.res && req.method == 'GET') {
var res = req.res;
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) {

View File

@@ -88,7 +88,8 @@ 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' ) {
@@ -98,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 + "'");
}
@@ -113,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') ) {

153
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "windshaft-cartodb",
"version": "2.3.0",
"version": "2.5.0",
"dependencies": {
"cartodb-psql": {
"version": "0.4.0",
@@ -76,9 +76,9 @@
"from": "fastly-purge@~1.0.0",
"dependencies": {
"request": {
"version": "2.55.0",
"version": "2.58.0",
"from": "request@^2.55.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.55.0.tgz",
"resolved": "https://registry.npmjs.org/request/-/request-2.58.0.tgz",
"dependencies": {
"bl": {
"version": "0.9.4",
@@ -114,9 +114,14 @@
}
},
"caseless": {
"version": "0.9.0",
"from": "caseless@~0.9.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz"
"version": "0.10.0",
"from": "caseless@~0.10.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.10.0.tgz"
},
"extend": {
"version": "2.0.1",
"from": "extend@~2.0.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-2.0.1.tgz"
},
"forever-agent": {
"version": "0.6.1",
@@ -124,31 +129,43 @@
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
},
"form-data": {
"version": "0.2.0",
"from": "form-data@~0.2.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz",
"version": "1.0.0-rc1",
"from": "form-data@~1.0.0-rc1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc1.tgz",
"dependencies": {
"async": {
"version": "0.9.0",
"from": "async@~0.9.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz"
"version": "1.2.1",
"from": "async@^1.2.1",
"resolved": "https://registry.npmjs.org/async/-/async-1.2.1.tgz"
},
"mime-types": {
"version": "2.1.1",
"from": "mime-types@^2.1.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.1.tgz",
"dependencies": {
"mime-db": {
"version": "1.13.0",
"from": "mime-db@~1.13.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.13.0.tgz"
}
}
}
}
},
"json-stringify-safe": {
"version": "5.0.0",
"version": "5.0.1",
"from": "json-stringify-safe@~5.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz"
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
},
"mime-types": {
"version": "2.0.11",
"version": "2.0.14",
"from": "mime-types@~2.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.11.tgz",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz",
"dependencies": {
"mime-db": {
"version": "1.9.1",
"from": "mime-db@~1.9.1",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.9.1.tgz"
"version": "1.12.0",
"from": "mime-db@~1.12.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz"
}
}
},
@@ -158,9 +175,9 @@
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
},
"qs": {
"version": "2.4.2",
"from": "qs@~2.4.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-2.4.2.tgz"
"version": "3.1.0",
"from": "qs@~3.1.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-3.1.0.tgz"
},
"tunnel-agent": {
"version": "0.4.0",
@@ -168,14 +185,14 @@
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.0.tgz"
},
"tough-cookie": {
"version": "1.1.0",
"version": "2.0.0",
"from": "tough-cookie@>=0.12.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-1.1.0.tgz"
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.0.0.tgz"
},
"http-signature": {
"version": "0.10.1",
"from": "http-signature@~0.10.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz",
"version": "0.11.0",
"from": "http-signature@~0.11.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz",
"dependencies": {
"assert-plus": {
"version": "0.1.5",
@@ -195,9 +212,9 @@
}
},
"oauth-sign": {
"version": "0.6.0",
"from": "oauth-sign@~0.6.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz"
"version": "0.8.0",
"from": "oauth-sign@~0.8.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.tgz"
},
"hawk": {
"version": "2.3.1",
@@ -205,14 +222,14 @@
"resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz",
"dependencies": {
"hoek": {
"version": "2.13.0",
"version": "2.14.0",
"from": "hoek@2.x.x",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.13.0.tgz"
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.14.0.tgz"
},
"boom": {
"version": "2.7.1",
"version": "2.8.0",
"from": "boom@2.x.x",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.7.1.tgz"
"resolved": "https://registry.npmjs.org/boom/-/boom-2.8.0.tgz"
},
"cryptiles": {
"version": "2.0.4",
@@ -237,14 +254,14 @@
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.4.tgz"
},
"combined-stream": {
"version": "0.0.7",
"from": "combined-stream@~0.0.5",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
"version": "1.0.5",
"from": "combined-stream@~1.0.1",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
"dependencies": {
"delayed-stream": {
"version": "0.0.5",
"from": "delayed-stream@0.0.5",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz"
"version": "1.0.0",
"from": "delayed-stream@~1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
}
}
},
@@ -254,14 +271,14 @@
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
},
"har-validator": {
"version": "1.7.0",
"from": "har-validator@^1.4.0",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.7.0.tgz",
"version": "1.7.1",
"from": "har-validator@^1.6.1",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.7.1.tgz",
"dependencies": {
"bluebird": {
"version": "2.9.25",
"from": "bluebird@^2.9.25",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.25.tgz"
"version": "2.9.30",
"from": "bluebird@^2.9.26",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.30.tgz"
},
"chalk": {
"version": "1.0.0",
@@ -328,7 +345,7 @@
},
"is-my-json-valid": {
"version": "2.12.0",
"from": "is-my-json-valid@^2.10.1",
"from": "is-my-json-valid@^2.12.0",
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.0.tgz",
"dependencies": {
"generate-function": {
@@ -367,7 +384,7 @@
}
},
"log4js": {
"version": "0.6.21",
"version": "0.6.25",
"from": "https://github.com/CartoDB/log4js-node/tarball/cdb",
"resolved": "https://github.com/CartoDB/log4js-node/tarball/cdb",
"dependencies": {
@@ -401,6 +418,16 @@
"from": "inherits@~2.0.1"
}
}
},
"semver": {
"version": "4.3.6",
"from": "semver@~4.3.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
},
"underscore": {
"version": "1.8.2",
"from": "underscore@1.8.2",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.2.tgz"
}
}
},
@@ -464,9 +491,9 @@
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz"
},
"windshaft": {
"version": "0.43.0",
"from": "windshaft@0.43.0",
"resolved": "https://registry.npmjs.org/windshaft/-/windshaft-0.43.0.tgz",
"version": "0.45.0",
"from": "windshaft@0.45.0",
"resolved": "https://registry.npmjs.org/windshaft/-/windshaft-0.45.0.tgz",
"dependencies": {
"chronograph": {
"version": "0.1.0",
@@ -579,9 +606,9 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz"
},
"json-stringify-safe": {
"version": "5.0.0",
"version": "5.0.1",
"from": "json-stringify-safe@~5.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz"
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
},
"forever-agent": {
"version": "0.5.2",
@@ -594,9 +621,9 @@
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
},
"tough-cookie": {
"version": "1.1.0",
"version": "2.0.0",
"from": "tough-cookie@>=0.12.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-1.1.0.tgz"
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.0.0.tgz"
},
"form-data": {
"version": "0.1.4",
@@ -616,9 +643,9 @@
}
},
"async": {
"version": "0.9.0",
"version": "0.9.2",
"from": "async@~0.9.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz"
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
}
}
},
@@ -2817,16 +2844,16 @@
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
},
"async": {
"version": "0.9.0",
"version": "0.9.2",
"from": "async@~0.9.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz"
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
}
}
},
"json-stringify-safe": {
"version": "5.0.0",
"version": "5.0.1",
"from": "json-stringify-safe@~5.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz"
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
},
"mime-types": {
"version": "1.0.2",
@@ -2849,9 +2876,9 @@
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.0.tgz"
},
"tough-cookie": {
"version": "1.1.0",
"version": "2.0.0",
"from": "tough-cookie@>=0.12.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-1.1.0.tgz"
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.0.0.tgz"
},
"http-signature": {
"version": "0.10.1",

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "2.3.0",
"version": "2.5.0",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
@@ -24,7 +24,7 @@
"dependencies": {
"underscore" : "~1.6.0",
"dot": "~1.0.2",
"windshaft": "0.43.0",
"windshaft": "0.45.0",
"step": "~0.0.5",
"queue-async": "~1.0.7",
"request": "~2.9.203",

View File

@@ -174,7 +174,7 @@ describe('render limits', function() {
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { error: 'Render timed out' });
assert.deepEqual(parsed, { errors: ['Render timed out'] });
done();
}
);

View File

@@ -69,6 +69,7 @@ suite(suiteName, function() {
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);
@@ -131,7 +132,7 @@ suite(suiteName, function() {
}, {}, 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);
});
@@ -1011,6 +1012,7 @@ suite(suiteName, 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(':');
@@ -1091,6 +1093,7 @@ suite(suiteName, 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(':');

View File

@@ -108,6 +108,7 @@ describe('tests from old api translated to multilayer', function() {
function(res) {
var parsed = JSON.parse(res.body);
assert.ok(parsed.layergroupid);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
done();
}
);
@@ -128,6 +129,7 @@ describe('tests from old api translated to multilayer', function() {
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();
@@ -150,6 +152,7 @@ describe('tests from old api translated to multilayer', function() {
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();
@@ -246,6 +249,8 @@ describe('tests from old api translated to multilayer', function() {
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();
}
);

View File

@@ -44,6 +44,7 @@ describe('named_layers', function() {
},
layergroup: {
layers: [
wadusLayer,
wadusLayer
]
}
@@ -634,6 +635,88 @@ describe('named_layers', function() {
});
it('should return metadata for named layers', 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};

View File

@@ -88,8 +88,8 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 403);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'), res.body);
err = parsed.error;
assert.ok(parsed.hasOwnProperty('errors'), res.body);
err = parsed.errors[0];
assert.ok(err.match(/only.*authenticated.*user/i),
'Unexpected error response: ' + err);
post_request_1.url += '?api_key=1234';
@@ -113,9 +113,9 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 400, res.body);
var parsedBody = JSON.parse(res.body);
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
assert.ok(parsedBody.error.match(/already exists/i),
'Unexpected error for pre-existing template name: ' + parsedBody.error);
assert.ok(parsedBody.hasOwnProperty('errors'), res.body);
assert.ok(parsedBody.errors[0].match(/already exists/i),
'Unexpected error for pre-existing template name: ' + parsedBody.errors);
return null;
},
function finish(err) {
@@ -172,10 +172,10 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 400, res.body);
var parsedBody = JSON.parse(res.body);
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
assert.ok(parsedBody.hasOwnProperty('errors'), res.body);
var re = /invalid.*authentication.*missing/i;
assert.ok(parsedBody.error.match(re),
'Error for invalid authentication does not match ' + re + ': ' + parsedBody.error);
assert.ok(parsedBody.errors[0].match(re),
'Error for invalid authentication does not match ' + re + ': ' + parsedBody.errors);
return null;
},
function postTemplate2(err)
@@ -202,10 +202,10 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 400, res.body);
var parsedBody = JSON.parse(res.body);
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
assert.ok(parsedBody.hasOwnProperty('errors'), res.body);
var re = new RegExp(/invalid.*authentication.*missing/i);
assert.ok(parsedBody.error.match(re),
'Error for invalid authentication does not match ' + re + ': ' + parsedBody.error);
assert.ok(parsedBody.errors[0].match(re),
'Error for invalid authentication does not match ' + re + ': ' + parsedBody.errors);
return null;
},
function postTemplateValid(err)
@@ -253,12 +253,12 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 400, res.statusCode + ": " + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
var re = /invalid.*authentication.*missing/i;
assert.ok(parsed.error.match(re),
assert.ok(parsed.errors[0].match(re),
'Error for invalid authentication on PUT does not match ' +
re + ': ' + parsed.error);
re + ': ' + parsed.errors);
var del_request = {
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
method: 'DELETE',
@@ -425,9 +425,9 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
assert.ok(parsed.hasOwnProperty('errors'),
'Missing error from response: ' + res.body);
err = parsed.error;
err = parsed.errors[0];
assert.ok(err.match(/authenticated user/), err);
var next = this;
var get_request = {
@@ -523,9 +523,9 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 400, res.statusCode + ": " + res.body);
var parsedBody = JSON.parse(res.body);
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
assert.ok(parsedBody.error.match(/cannot update name/i),
'Unexpected error for invalid update: ' + parsedBody.error);
assert.ok(parsedBody.hasOwnProperty('errors'), res.body);
assert.ok(parsedBody.errors[0].match(/cannot update name/i),
'Unexpected error for invalid update: ' + parsedBody.errors);
var put_request = {
url: '/api/v1/map/named/unexistent/?api_key=1234',
method: 'PUT',
@@ -541,9 +541,9 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 400, res.statusCode + ": " + res.body);
var parsedBody = JSON.parse(res.body);
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
assert.ok(parsedBody.error.match(/cannot update name/i),
'Unexpected error for invalid update: ' + parsedBody.error);
assert.ok(parsedBody.hasOwnProperty('errors'), res.body);
assert.ok(parsedBody.errors[0].match(/cannot update name/i),
'Unexpected error for invalid update: ' + parsedBody.errors);
var put_request = {
url: '/api/v1/map/named/' + tpl_id + '/?api_key=1234',
method: 'PUT',
@@ -630,9 +630,9 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 403, res.statusCode + ": " + res.body);
var parsedBody = JSON.parse(res.body);
assert.ok(parsedBody.hasOwnProperty('error'), res.body);
assert.ok(parsedBody.error.match(/only.*authenticated.*user/i),
'Unexpected error for unauthenticated template get: ' + parsedBody.error);
assert.ok(parsedBody.hasOwnProperty('errors'), res.body);
assert.ok(parsedBody.errors[0].match(/only.*authenticated.*user/i),
'Unexpected error for unauthenticated template get: ' + parsedBody.errors);
var get_request = {
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
method: 'GET',
@@ -735,10 +735,10 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 403, res.statusCode + ": " + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/only.*authenticated.*user/i),
'Unexpected error for unauthenticated template get: ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/only.*authenticated.*user/i),
'Unexpected error for unauthenticated template get: ' + parsed.errors);
var del_request = {
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
method: 'DELETE',
@@ -767,10 +767,10 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/cannot find/i),
'Unexpected error for missing template: ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/cannot find/i),
'Unexpected error for missing template: ' + parsed.errors);
return null;
},
function finish(err) {
@@ -863,10 +863,10 @@ describe('template_api', function() {
assert.equal(res.statusCode, 403,
'Unexpected success instanciating template with no auth: ' + res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/unauthorized/i),
'Unexpected error for unauthorized instance : ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/unauthorized/i),
'Unexpected error for unauthorized instance : ' + parsed.errors);
var post_request = {
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
method: 'POST',
@@ -882,8 +882,8 @@ describe('template_api', function() {
if ( err ) throw err;
assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'), "Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/not found/i), 'Unexpected error for forbidden instance : ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'), "Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/not found/i), 'Unexpected error for forbidden instance : ' + parsed.errors);
var post_request = {
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
method: 'POST',
@@ -922,10 +922,10 @@ describe('template_api', function() {
assert.equal(res.statusCode, 403,
'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/permission denied/i),
'Unexpected error for unauthorized instance (expected /permission denied/): ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/permission denied/i),
'Unexpected error for unauthorized instance (expected /permission denied/): ' + parsed.errors);
var get_request = {
url: '/api/v1/map/' + layergroupid + '/0/0/0.png?auth_token=valid1',
method: 'GET',
@@ -962,10 +962,10 @@ describe('template_api', function() {
assert.equal(res.statusCode, 403,
'Unexpected error for authorized instance: ' + res.statusCode + ' -- ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/cannot use/i),
'Unexpected error for unauthorized instance (expected /cannot use/): ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/cannot use/i),
'Unexpected error for unauthorized instance (expected /cannot use/): ' + parsed.errors);
return null;
},
function deleteTemplate(err)
@@ -1089,10 +1089,10 @@ describe('template_api', function() {
assert.equal(res.statusCode, 403,
'Unexpected success instanciating template with no auth: ' + res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/unauthorized/i),
'Unexpected error for unauthorized instance : ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/unauthorized/i),
'Unexpected error for unauthorized instance : ' + parsed.errors);
var post_request = {
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
method: 'POST',
@@ -1131,10 +1131,10 @@ describe('template_api', function() {
assert.equal(res.statusCode, 403,
'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/permission denied/i),
'Unexpected error for unauthorized instance (expected /permission denied): ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/permission denied/i),
'Unexpected error for unauthorized instance (expected /permission denied): ' + parsed.errors);
var get_request = {
url: '/api/v1/map/' + layergroupid + ':cb1/0/0/0/0.json.torque?auth_token=valid1',
method: 'GET',
@@ -1297,10 +1297,10 @@ describe('template_api', function() {
assert.equal(res.statusCode, 403,
'Unexpected success instanciating template with no auth: ' + res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/unauthorized/i),
'Unexpected error for unauthorized instance : ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/unauthorized/i),
'Unexpected error for unauthorized instance : ' + parsed.errors);
var post_request = {
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
method: 'POST',
@@ -1321,6 +1321,7 @@ describe('template_api', function() {
layergroupid = parsed.layergroupid;
assert.ok(layergroupid.match(/^localhost@/),
"Returned layergroupid does not start with signer name: " + layergroupid);
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
assert.ok(parsed.hasOwnProperty('last_updated'),
"Missing 'last_updated' from response body: " + res.body);
// TODO: check value of last_updated ?
@@ -1339,10 +1340,10 @@ describe('template_api', function() {
assert.equal(res.statusCode, 403,
'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body);
assert.ok(parsed.hasOwnProperty('error'),
"Missing 'error' from response body: " + res.body);
assert.ok(parsed.error.match(/permission denied/i),
'Unexpected error for unauthorized getAttributes (expected /permission denied/): ' + parsed.error);
assert.ok(parsed.hasOwnProperty('errors'),
"Missing 'errors' from response body: " + res.body);
assert.ok(parsed.errors[0].match(/permission denied/i),
'Unexpected error for unauthorized getAttributes (expected /permission denied/): ' + parsed.errors);
var get_request = {
url: '/api/v1/map/' + layergroupid + ':cb1/0/attributes/5?auth_token=valid2',
method: 'GET',
@@ -1608,7 +1609,6 @@ describe('template_api', function() {
function checkInstanciation(err, res)
{
if ( err ) throw err;
console.log(err, res.body, res.headers);
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
helper.checkCache(res);
@@ -2072,7 +2072,7 @@ describe('template_api', function() {
return done(err);
}
assert.deepEqual(JSON.parse(res.body), {
error: "Invalid or nonexistent map configuration token '" + nonexistentToken + "'"
errors: ["Invalid or nonexistent map configuration token '" + nonexistentToken + "'"]
});
done();

View File

@@ -59,30 +59,50 @@ describe('template_maps', function() {
);
});
it('does not accept template with invalid name', function(done) {
var tmap = new TemplateMaps(redis_pool);
assert.ok(tmap);
var tpl = { version:'0.0.1',
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 + "'"));
describe('naming', function() {
function createTemplate(name) {
return {
version:'0.0.1',
name: name,
auth: {},
layergroup: {
layers: [
wadusLayer
]
}
};
}
else if ( ! err.message.match(/template.*name/i) ) {
done(new Error("Unexpected error message with invalid name '" + n + "': " + err));
}
else {
testNext();
}
});
};
testNext();
});
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);