Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70dab149ba | ||
|
|
ed4b44a78a | ||
|
|
de1aaf3808 | ||
|
|
769aee1107 | ||
|
|
3663e6d12a | ||
|
|
a6d9984453 | ||
|
|
f49d7478d7 | ||
|
|
b4a1c9d648 | ||
|
|
692246ec44 | ||
|
|
04146f897d | ||
|
|
a34658c97f | ||
|
|
8d37e00869 | ||
|
|
584d6ae9cf | ||
|
|
421e611356 | ||
|
|
f078713d28 | ||
|
|
a8d31d52cf | ||
|
|
d9213b2fe2 | ||
|
|
091efe52fc | ||
|
|
a5c508733a | ||
|
|
ce944d9a7d | ||
|
|
8321b5adba | ||
|
|
667c972308 | ||
|
|
3dbe05be3a | ||
|
|
2d4ce19250 | ||
|
|
3b3e0c0acd | ||
|
|
c3ddb933bb | ||
|
|
6aae60ece7 | ||
|
|
6b3dc8ece0 | ||
|
|
7dd231a8c9 | ||
|
|
35a3219012 | ||
|
|
7598e6ab4b | ||
|
|
75870dc6c1 | ||
|
|
42900b5d0e | ||
|
|
2cc4161239 | ||
|
|
fc8f3fdf27 | ||
|
|
24b76208ac | ||
|
|
0de272b195 | ||
|
|
7faf40004c | ||
|
|
88ae2d473a | ||
|
|
337b47685c | ||
|
|
ed3f9be655 | ||
|
|
248c6d5f22 | ||
|
|
dd4aa09d21 | ||
|
|
132f2226ca | ||
|
|
2eb6e95fed | ||
|
|
db8130be4f | ||
|
|
379b649e95 | ||
|
|
e5619492ef | ||
|
|
cc76ccc626 | ||
|
|
3d6512dd11 | ||
|
|
b19d97e01f | ||
|
|
25931a618b | ||
|
|
ffab576399 | ||
|
|
e7067ab9cf | ||
|
|
7cfcf6d579 | ||
|
|
a4b586055a | ||
|
|
5ad1e1b645 | ||
|
|
01ed513a79 | ||
|
|
504f68b8aa | ||
|
|
cbb08f5642 | ||
|
|
a4b5d681ce | ||
|
|
02f93f3a14 | ||
|
|
ad2f4573f8 | ||
|
|
06604cd738 | ||
|
|
089be35b5d | ||
|
|
bbcb335d60 | ||
|
|
6ef2e0bb5f | ||
|
|
d8202d881d | ||
|
|
aae814a156 | ||
|
|
49bcc5368d | ||
|
|
555e04f9e7 | ||
|
|
3f6f2e4e23 | ||
|
|
abffc4b067 | ||
|
|
363cb0b679 | ||
|
|
d26910ba9c | ||
|
|
74b2f305ea | ||
|
|
6c2f893651 | ||
|
|
faaf121eb6 | ||
|
|
83ab65163d | ||
|
|
9dcd5ff332 | ||
|
|
c6635f63c1 | ||
|
|
56213219e4 | ||
|
|
7c2dc20dbe | ||
|
|
c8e8317ea4 | ||
|
|
8509796743 | ||
|
|
90aaed0f2c | ||
|
|
48be15b742 | ||
|
|
a95b3f2f99 | ||
|
|
b2cc7ab84f | ||
|
|
eb3414f07f | ||
|
|
292dad130d | ||
|
|
ec41cddb19 | ||
|
|
5871f8290d | ||
|
|
33089be2cd | ||
|
|
d351c8d14c | ||
|
|
82446e5ffa | ||
|
|
b786164e8a | ||
|
|
f9cbb3aac8 | ||
|
|
a66c19c6c7 | ||
|
|
94d1667d70 | ||
|
|
3399db1cff | ||
|
|
874ea99d19 | ||
|
|
7022fb87b4 | ||
|
|
7c1e2a6af0 | ||
|
|
2f011c3266 | ||
|
|
4762aa0897 | ||
|
|
f30f83331f | ||
|
|
3695e1e3e5 | ||
|
|
585b5929aa | ||
|
|
0185cdf785 | ||
|
|
8d22ca66ba | ||
|
|
b0eacb2a79 | ||
|
|
9b40370794 | ||
|
|
95f3d58383 | ||
|
|
0f0cde1093 | ||
|
|
ca56df5cfe | ||
|
|
d8a4209768 | ||
|
|
40712a2e62 | ||
|
|
acb9ce33b1 | ||
|
|
5e43a7145a | ||
|
|
39bd6694f2 | ||
|
|
5de8c4f9c3 | ||
|
|
f03d98cd0d | ||
|
|
6331bebb30 | ||
|
|
fdd4c4aaa0 | ||
|
|
8656fcd8d1 | ||
|
|
f2f6b9d49c | ||
|
|
82f1e6753b | ||
|
|
7ed717607a | ||
|
|
0ec9491d21 | ||
|
|
416970c819 | ||
|
|
ccc28f3617 | ||
|
|
5bac36b30f | ||
|
|
ef3ffddec7 | ||
|
|
e6ba467d98 | ||
|
|
314508bcd8 | ||
|
|
da18506e41 | ||
|
|
5eaee0b71e | ||
|
|
bd93e7dc7e | ||
|
|
2c762813ba | ||
|
|
3caa1d9c4a | ||
|
|
b0c924ca03 | ||
|
|
f6f59023b4 | ||
|
|
9dc4e7c955 | ||
|
|
faa44e54ae | ||
|
|
bfb743b851 | ||
|
|
dad2e92dd3 | ||
|
|
59c312ea40 | ||
|
|
48c5a458f3 | ||
|
|
c0830862c8 | ||
|
|
42deb7abbe | ||
|
|
62deda6470 | ||
|
|
3e0981978a | ||
|
|
35e5170907 | ||
|
|
8eba5dcc01 | ||
|
|
5c2248d419 | ||
|
|
102b11b1b5 | ||
|
|
26df09b13f | ||
|
|
8867cdbc02 | ||
|
|
521b441da5 | ||
|
|
59ca00b33b | ||
|
|
7c7d606aa7 | ||
|
|
46587e3cf1 | ||
|
|
603ef4044c | ||
|
|
2e3abfb2cd | ||
|
|
47ccb7ded8 | ||
|
|
7e14247ea9 | ||
|
|
5823859b2a | ||
|
|
7b21bd26d0 | ||
|
|
4ac224688c | ||
|
|
e84d88b7a3 | ||
|
|
cda2616a8a | ||
|
|
11aa4d12bd | ||
|
|
18dbeea003 | ||
|
|
3e916c6054 | ||
|
|
fc420c2c0f | ||
|
|
140441b777 | ||
|
|
5db0e9c8d8 | ||
|
|
963737d3fb | ||
|
|
890f0d1ef6 | ||
|
|
6b0ab45e63 | ||
|
|
0b475ab5e2 | ||
|
|
97972ac73f | ||
|
|
c3b38b2f60 | ||
|
|
b4e06ec1ac | ||
|
|
d0a8bd428f | ||
|
|
251fe96509 | ||
|
|
32986e3ebd | ||
|
|
f106f27df4 | ||
|
|
3539b658fb | ||
|
|
abf33a1c68 | ||
|
|
fc6790ea1e | ||
|
|
ac6f0e1c67 | ||
|
|
041cd40ec2 | ||
|
|
8721f56269 | ||
|
|
1d3045c799 | ||
|
|
52a1ed869c | ||
|
|
04f60baec5 | ||
|
|
a8de436424 | ||
|
|
ee7917676b | ||
|
|
c7780e9f42 | ||
|
|
3edd7b8b01 | ||
|
|
455202cd1a | ||
|
|
8bdb82c7be | ||
|
|
fa503ee66a | ||
|
|
e1a2ee2381 | ||
|
|
b82d26527a | ||
|
|
1c50dd6b48 | ||
|
|
b0e9df1400 | ||
|
|
6ebf51ce45 | ||
|
|
d9a34f3384 | ||
|
|
8136a1e136 | ||
|
|
41f3606572 | ||
|
|
ea0542dcb1 | ||
|
|
a4dbc1bac2 | ||
|
|
065f56e161 | ||
|
|
6b5d6648de | ||
|
|
95538707c9 | ||
|
|
4c76a921b1 | ||
|
|
85c1c987af | ||
|
|
bde86323fd | ||
|
|
880e3f388d | ||
|
|
c1535b1a12 | ||
|
|
232ff1ba33 | ||
|
|
1b63dcd4e5 | ||
|
|
b32a0a6547 | ||
|
|
d634be0c30 | ||
|
|
f9fe3ace37 | ||
|
|
6cd8131888 | ||
|
|
0ea76f7d15 | ||
|
|
51e5b5c255 | ||
|
|
cedcc094e6 | ||
|
|
bbe8d4e820 | ||
|
|
5aa98c4ab2 | ||
|
|
d6a9103779 | ||
|
|
2e7784ddf2 | ||
|
|
086be461b2 | ||
|
|
80604b739a | ||
|
|
d88fbbaa87 | ||
|
|
7db0744f67 | ||
|
|
d1fcd797a3 | ||
|
|
2c703e5c16 | ||
|
|
e68ba95fed | ||
|
|
83d00a8aca | ||
|
|
479e8970a1 | ||
|
|
cf5e797f90 |
36
NEWS.md
36
NEWS.md
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## 5.4.0
|
||||
Released 2018-03-15
|
||||
- Upgrades Windshaft to 4.5.7 ([Mapnik top metrics](https://github.com/CartoDB/Windshaft/pull/597), [AttributesBackend allows multiple features if all the attributes are the same](https://github.com/CartoDB/Windshaft/pull/602))
|
||||
- Implemented middleware to authorize users via new Api Key system
|
||||
- Keep the old authorization system as fallback
|
||||
- Aggregation widget: Remove NULL categories in 'count' aggregations too
|
||||
- Update request to 2.85.0
|
||||
- Update camshaft to 0.61.4 (Fixes for AOI and Merge analyses)
|
||||
- Update windshaft to 4.6.0, which in turn updates @carto/mapnik to 3.6.2-carto.4 and related dependencies. It brings in a cache for rasterized symbols. See https://github.com/CartoDB/node-mapnik/blob/v3.6.2-carto/CHANGELOG.carto.md#362-carto4
|
||||
- PostGIS: Variables in postgis SQL queries must now additionally be wrapped in `!` (refs [#29](https://github.com/CartoDB/mapnik/issues/29), [mapnik/#3618](https://github.com/mapnik/mapnik/pull/3618)):
|
||||
```sql
|
||||
-- Before
|
||||
SELECT ... WHERE trait = @variable
|
||||
|
||||
-- Now
|
||||
SELECT ... WHERE trait = !@variable!
|
||||
```
|
||||
|
||||
## 5.3.1
|
||||
Released 2018-02-13
|
||||
- Improve the speed of the aggregation dataview #865
|
||||
|
||||
## 5.3.0
|
||||
Released 2018-02-12
|
||||
- Upgrades redis-mpool to 0.5.0
|
||||
- Upgrades windshaft to 4.5.2
|
||||
- Upgrades cartodb-redis to 0.15.0
|
||||
- Adds metrics option to the Mapnik renderer
|
||||
- Upgrades camshadft to 0.61.2
|
||||
|
||||
## 5.2.1
|
||||
Released 2018-02-01
|
||||
|
||||
Bug Fixes:
|
||||
- Allow use of aggregation with attributes #861
|
||||
|
||||
## 5.2.0
|
||||
Released 2018-02-01
|
||||
|
||||
|
||||
@@ -205,11 +205,12 @@ var config = {
|
||||
// much closer to the original geometry
|
||||
removeRepeatedPoints: false // this requires postgis >=2.2
|
||||
},
|
||||
|
||||
// If enabled Mapnik will reuse the features retrieved from the database
|
||||
// instead of requesting them once per style inside a layer
|
||||
'cache-features': true
|
||||
'cache-features': true,
|
||||
|
||||
// Require metrics to the renderer
|
||||
metrics: false
|
||||
},
|
||||
http: {
|
||||
timeout: 2000, // the timeout in ms for a http tile request
|
||||
|
||||
@@ -202,7 +202,10 @@ var config = {
|
||||
|
||||
// If enabled Mapnik will reuse the features retrieved from the database
|
||||
// instead of requesting them once per style inside a layer
|
||||
'cache-features': true
|
||||
'cache-features': true,
|
||||
|
||||
// Require metrics to the renderer
|
||||
metrics: false
|
||||
|
||||
},
|
||||
http: {
|
||||
|
||||
@@ -202,7 +202,10 @@ var config = {
|
||||
|
||||
// If enabled Mapnik will reuse the features retrieved from the database
|
||||
// instead of requesting them once per style inside a layer
|
||||
'cache-features': true
|
||||
'cache-features': true,
|
||||
|
||||
// Require metrics to the renderer
|
||||
metrics: false
|
||||
|
||||
},
|
||||
http: {
|
||||
|
||||
@@ -201,8 +201,10 @@ var config = {
|
||||
|
||||
// If enabled Mapnik will reuse the features retrieved from the database
|
||||
// instead of requesting them once per style inside a layer
|
||||
'cache-features': true
|
||||
'cache-features': true,
|
||||
|
||||
// Require metrics to the renderer
|
||||
metrics: false
|
||||
},
|
||||
http: {
|
||||
timeout: 2000, // the timeout in ms for a http tile request
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var assert = require('assert');
|
||||
var step = require('step');
|
||||
var _ = require('underscore'); // AUTH_FALLBACK
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -47,39 +46,113 @@ AuthApi.prototype.authorizedBySigner = function(res, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
function isValidApiKey(apikey) {
|
||||
return apikey.type &&
|
||||
apikey.user &&
|
||||
apikey.databasePassword &&
|
||||
apikey.databaseRole;
|
||||
}
|
||||
|
||||
// Check if a request is authorized by api_key
|
||||
//
|
||||
// @param user
|
||||
// @param req express request object
|
||||
// @param res express response object
|
||||
// @param callback function(err, authorized)
|
||||
// NOTE: authorized is expected to be 0 or 1 (integer)
|
||||
//
|
||||
AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
|
||||
var givenKey = req.query.api_key || req.query.map_key;
|
||||
if ( ! givenKey && req.body ) {
|
||||
// check also in request body
|
||||
givenKey = req.body.api_key || req.body.map_key;
|
||||
}
|
||||
if ( ! givenKey ) {
|
||||
return callback(null, 0); // no api key, no authorization...
|
||||
AuthApi.prototype.authorizedByAPIKey = function(user, res, callback) {
|
||||
const apikeyToken = res.locals.api_key;
|
||||
const basicAuthUsername = res.locals.basicAuthUsername;
|
||||
|
||||
if ( ! apikeyToken ) {
|
||||
return callback(null, false); // no api key, no authorization...
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this.metadataBackend.getApikey(user, apikeyToken, (err, apikey) => {
|
||||
if (err) {
|
||||
if (isNameNotFoundError(err)) {
|
||||
err.http_status = 404;
|
||||
}
|
||||
|
||||
step(
|
||||
function () {
|
||||
self.metadataBackend.getUserMapKey(user, this);
|
||||
},
|
||||
function checkApiKey(err, val){
|
||||
assert.ifError(err);
|
||||
return val && givenKey === val;
|
||||
},
|
||||
function finish(err, authorized) {
|
||||
callback(err, authorized);
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
|
||||
//Remove this block when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
apikey.databaseRole = composeUserDatabase(apikey);
|
||||
apikey.databasePassword = composeDatabasePassword(apikey);
|
||||
|
||||
if ( !isValidApiKey(apikey)) {
|
||||
const error = new Error('Unauthorized');
|
||||
error.type = 'auth';
|
||||
error.subtype = 'api-key-not-found';
|
||||
error.http_status = 401;
|
||||
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
if (!usernameMatches(basicAuthUsername, res.locals.user)) {
|
||||
const error = new Error('Forbidden');
|
||||
error.type = 'auth';
|
||||
error.subtype = 'api-key-username-mismatch';
|
||||
error.http_status = 403;
|
||||
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
if (!apikey.grantsMaps) {
|
||||
const error = new Error('Forbidden');
|
||||
error.type = 'auth';
|
||||
error.subtype = 'api-key-does-not-grant-access';
|
||||
error.http_status = 403;
|
||||
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
return callback(null, true);
|
||||
});
|
||||
};
|
||||
|
||||
//Remove this block when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
function composeUserDatabase (apikey) {
|
||||
if (shouldComposeUserDatabase(apikey)) {
|
||||
return _.template(global.environment.postgres_auth_user, apikey);
|
||||
}
|
||||
|
||||
return apikey.databaseRole;
|
||||
}
|
||||
|
||||
//Remove this block when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
function composeDatabasePassword (apikey) {
|
||||
if (shouldComposeDatabasePassword(apikey)) {
|
||||
return global.environment.postgres.password;
|
||||
}
|
||||
|
||||
return apikey.databasePassword;
|
||||
}
|
||||
|
||||
//Remove this block when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
function shouldComposeDatabasePassword (apikey) {
|
||||
return !apikey.databasePassword && global.environment.postgres.password;
|
||||
}
|
||||
|
||||
//Remove this block when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
function shouldComposeUserDatabase(apikey) {
|
||||
return !apikey.databaseRole && apikey.user_id && global.environment.postgres_auth_user;
|
||||
}
|
||||
|
||||
function isNameNotFoundError (err) {
|
||||
return err.message && -1 !== err.message.indexOf('name not found');
|
||||
}
|
||||
|
||||
function usernameMatches (basicAuthUsername, requestUsername) {
|
||||
return !(basicAuthUsername && (basicAuthUsername !== requestUsername));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access authorization
|
||||
*
|
||||
@@ -88,51 +161,57 @@ AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
|
||||
* @param callback function(err, allowed) is access allowed not?
|
||||
*/
|
||||
AuthApi.prototype.authorize = function(req, res, callback) {
|
||||
var self = this;
|
||||
var user = res.locals.user;
|
||||
|
||||
step(
|
||||
function () {
|
||||
self.authorizedByAPIKey(user, req, this);
|
||||
},
|
||||
function checkApiKey(err, authorized){
|
||||
req.profiler.done('authorizedByAPIKey');
|
||||
assert.ifError(err);
|
||||
this.authorizedByAPIKey(user, res, (err, isAuthorizedByApikey) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// if not authorized by api_key, continue
|
||||
if (!authorized) {
|
||||
// not authorized by api_key, check if authorized by signer
|
||||
return self.authorizedBySigner(res, this);
|
||||
}
|
||||
if (isAuthorizedByApikey) {
|
||||
return this.pgConnection.setDBAuth(user, res.locals, 'regular', function (err) {
|
||||
req.profiler.done('setDBAuth');
|
||||
|
||||
// authorized by api key, login as the given username and stop
|
||||
self.pgConnection.setDBAuth(user, res.locals, function(err) {
|
||||
callback(err, true); // authorized (or error)
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, true);
|
||||
});
|
||||
},
|
||||
function checkSignAuthorized(err, authorized) {
|
||||
}
|
||||
|
||||
this.authorizedBySigner(res, (err, isAuthorizedBySigner) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if ( ! authorized ) {
|
||||
// request not authorized by signer.
|
||||
if (isAuthorizedBySigner) {
|
||||
return this.pgConnection.setDBAuth(user, res.locals, 'master', function (err) {
|
||||
req.profiler.done('setDBAuth');
|
||||
|
||||
// if no signer name was given, let dbparams and
|
||||
// PostgreSQL do the rest.
|
||||
//
|
||||
if ( ! res.locals.signer ) {
|
||||
return callback(null, true); // authorized so far
|
||||
}
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// if signer name was given, return no authorization
|
||||
return callback(null, false);
|
||||
callback(null, true);
|
||||
});
|
||||
}
|
||||
|
||||
self.pgConnection.setDBAuth(user, res.locals, function(err) {
|
||||
req.profiler.done('setDBAuth');
|
||||
callback(err, true); // authorized (or error)
|
||||
});
|
||||
}
|
||||
);
|
||||
// if no signer name was given, use default api key
|
||||
if (!res.locals.signer) {
|
||||
return this.pgConnection.setDBAuth(user, res.locals, 'default', function (err) {
|
||||
req.profiler.done('setDBAuth');
|
||||
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, true);
|
||||
});
|
||||
}
|
||||
|
||||
// if signer name was given, return no authorization
|
||||
return callback(null, false);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
var assert = require('assert');
|
||||
var step = require('step');
|
||||
var PSQL = require('cartodb-psql');
|
||||
var _ = require('underscore');
|
||||
const debug = require('debug')('cachechan');
|
||||
|
||||
function PgConnection(metadataBackend) {
|
||||
this.metadataBackend = metadataBackend;
|
||||
@@ -20,45 +19,85 @@ module.exports = PgConnection;
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
PgConnection.prototype.setDBAuth = function(username, params, callback) {
|
||||
var self = this;
|
||||
|
||||
var user_params = {};
|
||||
var auth_user = global.environment.postgres_auth_user;
|
||||
var auth_pass = global.environment.postgres_auth_pass;
|
||||
step(
|
||||
function getId() {
|
||||
self.metadataBackend.getUserId(username, this);
|
||||
},
|
||||
function(err, user_id) {
|
||||
assert.ifError(err);
|
||||
user_params.user_id = user_id;
|
||||
var dbuser = _.template(auth_user, user_params);
|
||||
_.extend(params, {dbuser:dbuser});
|
||||
|
||||
// skip looking up user_password if postgres_auth_pass
|
||||
// doesn't contain the "user_password" label
|
||||
if (!auth_pass || ! auth_pass.match(/\buser_password\b/) ) {
|
||||
return null;
|
||||
PgConnection.prototype.setDBAuth = function(username, params, apikeyType, callback) {
|
||||
if (apikeyType === 'master') {
|
||||
this.metadataBackend.getMasterApikey(username, (err, apikey) => {
|
||||
if (err) {
|
||||
if (isNameNotFoundError(err)) {
|
||||
err.http_status = 404;
|
||||
}
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.metadataBackend.getUserDBPass(username, this);
|
||||
},
|
||||
function(err, user_password) {
|
||||
assert.ifError(err);
|
||||
user_params.user_password = user_password;
|
||||
if ( auth_pass ) {
|
||||
var dbpass = _.template(auth_pass, user_params);
|
||||
_.extend(params, {dbpassword:dbpass});
|
||||
params.dbuser = apikey.databaseRole;
|
||||
params.dbpassword = apikey.databasePassword;
|
||||
|
||||
//Remove this block when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
if (!params.dbuser && apikey.user_id && global.environment.postgres_auth_user) {
|
||||
params.dbuser = _.template(global.environment.postgres_auth_user, apikey);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
|
||||
return callback();
|
||||
});
|
||||
} else if (apikeyType === 'regular') { //Actually it can be any type of api key
|
||||
this.metadataBackend.getApikey(username, params.api_key, (err, apikey) => {
|
||||
if (err) {
|
||||
if (isNameNotFoundError(err)) {
|
||||
err.http_status = 404;
|
||||
}
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
params.dbuser = apikey.databaseRole;
|
||||
params.dbpassword = apikey.databasePassword;
|
||||
|
||||
//Remove this block when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
// master apikey has been recreated from user's metadata
|
||||
if (!params.dbuser && apikey.user_id && apikey.type === 'master' && global.environment.postgres_auth_user) {
|
||||
params.dbuser = _.template(global.environment.postgres_auth_user, apikey);
|
||||
}
|
||||
|
||||
//Remove this block when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
// default apikey has been recreated from user's metadata
|
||||
if (!params.dbpassword && global.environment.postgres.password) {
|
||||
params.dbpassword = global.environment.postgres.password;
|
||||
}
|
||||
|
||||
return callback();
|
||||
});
|
||||
} else if (apikeyType === 'default') {
|
||||
this.metadataBackend.getApikey(username, 'default_public', (err, apikey) => {
|
||||
if (err) {
|
||||
if (isNameNotFoundError(err)) {
|
||||
err.http_status = 404;
|
||||
}
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
params.dbuser = apikey.databaseRole;
|
||||
params.dbpassword = apikey.databasePassword;
|
||||
|
||||
//Remove this block when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
if (!params.dbpassword && global.environment.postgres.password) {
|
||||
params.dbpassword = global.environment.postgres.password;
|
||||
}
|
||||
|
||||
return callback();
|
||||
});
|
||||
} else {
|
||||
return callback(new Error(`Invalid Apikey type: ${apikeyType}, valid ones: master, regular, default`));
|
||||
}
|
||||
};
|
||||
|
||||
function isNameNotFoundError (err) {
|
||||
return err.message && -1 !== err.message.indexOf('name not found');
|
||||
}
|
||||
|
||||
|
||||
// Set db connection parameters to those for the given username
|
||||
//
|
||||
// @param dbowner cartodb username of database owner,
|
||||
@@ -71,36 +110,30 @@ PgConnection.prototype.setDBAuth = function(username, params, callback) {
|
||||
// @param callback function(err)
|
||||
//
|
||||
PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
|
||||
var self = this;
|
||||
// Add default database connection parameters
|
||||
// if none given
|
||||
_.defaults(params, {
|
||||
dbuser: global.environment.postgres.user,
|
||||
dbpassword: global.environment.postgres.password,
|
||||
// dbuser: global.environment.postgres.user,
|
||||
// dbpassword: global.environment.postgres.password,
|
||||
dbhost: global.environment.postgres.host,
|
||||
dbport: global.environment.postgres.port
|
||||
});
|
||||
step(
|
||||
function getConnectionParams() {
|
||||
self.metadataBackend.getUserDBConnectionParams(dbowner, this);
|
||||
},
|
||||
function extendParams(err, dbParams){
|
||||
assert.ifError(err);
|
||||
// we don't want null values or overwrite a non public user
|
||||
if (params.dbuser !== 'publicuser' || !dbParams.dbuser) {
|
||||
delete dbParams.dbuser;
|
||||
}
|
||||
if ( dbParams ) {
|
||||
_.extend(params, dbParams);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
this.metadataBackend.getUserDBConnectionParams(dbowner, (err, dbParams) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// we don’t want null values or overwrite a non public user
|
||||
if (params.dbuser !== 'publicuser' || !dbParams.dbuser) {
|
||||
delete dbParams.dbuser;
|
||||
}
|
||||
|
||||
if (dbParams) {
|
||||
_.extend(params, dbParams);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a `cartodb-psql` object for a given username.
|
||||
@@ -109,28 +142,37 @@ PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
|
||||
*/
|
||||
|
||||
PgConnection.prototype.getConnection = function(username, callback) {
|
||||
var self = this;
|
||||
debug("getConn1");
|
||||
|
||||
var params = {};
|
||||
|
||||
require('debug')('cachechan')("getConn1");
|
||||
step(
|
||||
function setAuth() {
|
||||
self.setDBAuth(username, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
assert.ifError(err);
|
||||
self.setDBConn(username, params, this);
|
||||
},
|
||||
function openConnection(err) {
|
||||
assert.ifError(err);
|
||||
return callback(err, new PSQL({
|
||||
user: params.dbuser,
|
||||
pass: params.dbpass,
|
||||
host: params.dbhost,
|
||||
port: params.dbport,
|
||||
dbname: params.dbname
|
||||
}));
|
||||
this.getDatabaseParams(username, (err, databaseParams) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
return callback(err, new PSQL({
|
||||
user: databaseParams.dbuser,
|
||||
pass: databaseParams.dbpass,
|
||||
host: databaseParams.dbhost,
|
||||
port: databaseParams.dbport,
|
||||
dbname: databaseParams.dbname
|
||||
}));
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
PgConnection.prototype.getDatabaseParams = function(username, callback) {
|
||||
const databaseParams = {};
|
||||
|
||||
this.setDBAuth(username, databaseParams, 'master', err => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
this.setDBConn(username, databaseParams, err => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, databaseParams);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
var assert = require('assert');
|
||||
var PSQL = require('cartodb-psql');
|
||||
var step = require('step');
|
||||
|
||||
function PgQueryRunner(pgConnection) {
|
||||
this.pgConnection = pgConnection;
|
||||
@@ -16,31 +14,23 @@ module.exports = PgQueryRunner;
|
||||
* @param {Function} callback function({Error}, {Array}) second argument is guaranteed to be an array
|
||||
*/
|
||||
PgQueryRunner.prototype.run = function(username, query, callback) {
|
||||
var self = this;
|
||||
|
||||
var params = {};
|
||||
|
||||
step(
|
||||
function setAuth() {
|
||||
self.pgConnection.setDBAuth(username, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
assert.ifError(err);
|
||||
self.pgConnection.setDBConn(username, params, this);
|
||||
},
|
||||
function executeQuery(err) {
|
||||
assert.ifError(err);
|
||||
var psql = new PSQL({
|
||||
user: params.dbuser,
|
||||
pass: params.dbpass,
|
||||
host: params.dbhost,
|
||||
port: params.dbport,
|
||||
dbname: params.dbname
|
||||
});
|
||||
psql.query(query, function(err, resultSet) {
|
||||
resultSet = resultSet || {};
|
||||
return callback(err, resultSet.rows || []);
|
||||
});
|
||||
this.pgConnection.getDatabaseParams(username, (err, databaseParams) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
|
||||
const psql = new PSQL({
|
||||
user: databaseParams.dbuser,
|
||||
pass: databaseParams.dbpass,
|
||||
host: databaseParams.dbhost,
|
||||
port: databaseParams.dbport,
|
||||
dbname: databaseParams.dbname
|
||||
});
|
||||
|
||||
psql.query(query, function (err, resultSet) {
|
||||
resultSet = resultSet || {};
|
||||
return callback(err, resultSet.rows || []);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,26 +12,26 @@ AnalysesController.prototype.register = function (app) {
|
||||
app.get(
|
||||
`${app.base_url_mapconfig}/analyses/catalog`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
this.prepareContext,
|
||||
this.createPGClient(),
|
||||
this.getDataFromQuery({ queryTemplate: catalogQueryTpl, key: 'catalog' }),
|
||||
this.getDataFromQuery({ queryTemplate: tablesQueryTpl, key: 'tables' }),
|
||||
this.prepareResponse(),
|
||||
this.setCacheControlHeader(),
|
||||
this.sendResponse(),
|
||||
this.unathorizedError()
|
||||
createPGClient(),
|
||||
getDataFromQuery({ queryTemplate: catalogQueryTpl, key: 'catalog' }),
|
||||
getDataFromQuery({ queryTemplate: tablesQueryTpl, key: 'tables' }),
|
||||
prepareResponse(),
|
||||
setCacheControlHeader(),
|
||||
sendResponse(),
|
||||
unauthorizedError()
|
||||
);
|
||||
};
|
||||
|
||||
AnalysesController.prototype.createPGClient = function () {
|
||||
function createPGClient () {
|
||||
return function createPGClientMiddleware (req, res, next) {
|
||||
res.locals.pg = new PSQL(dbParamsFromReqParams(res.locals));
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
AnalysesController.prototype.getDataFromQuery = function ({ queryTemplate, key }) {
|
||||
function getDataFromQuery({ queryTemplate, key }) {
|
||||
const readOnlyTransactionOn = true;
|
||||
|
||||
return function getCatalogMiddleware(req, res, next) {
|
||||
@@ -48,9 +48,9 @@ AnalysesController.prototype.getDataFromQuery = function ({ queryTemplate, key }
|
||||
next();
|
||||
}, readOnlyTransactionOn);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
AnalysesController.prototype.prepareResponse = function () {
|
||||
function prepareResponse () {
|
||||
return function prepareResponseMiddleware (req, res, next) {
|
||||
const { catalog, tables } = res.locals;
|
||||
|
||||
@@ -91,16 +91,16 @@ AnalysesController.prototype.prepareResponse = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
AnalysesController.prototype.setCacheControlHeader = function () {
|
||||
function setCacheControlHeader () {
|
||||
return function setCacheControlHeaderMiddleware (req, res, next) {
|
||||
res.set('Cache-Control', 'public,max-age=10,must-revalidate');
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
AnalysesController.prototype.sendResponse = function() {
|
||||
function sendResponse () {
|
||||
return function sendResponseMiddleware (req, res) {
|
||||
res.status(200);
|
||||
|
||||
@@ -110,9 +110,9 @@ AnalysesController.prototype.sendResponse = function() {
|
||||
res.json(res.body);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
AnalysesController.prototype.unathorizedError = function () {
|
||||
function unauthorizedError () {
|
||||
return function unathorizedErrorMiddleware(err, req, res, next) {
|
||||
if (err.message.match(/permission\sdenied/)) {
|
||||
err = new Error('Unauthorized');
|
||||
@@ -121,7 +121,7 @@ AnalysesController.prototype.unathorizedError = function () {
|
||||
|
||||
next(err);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const catalogQueryTpl = ctx => `
|
||||
SELECT analysis_def->>'type' as type, * FROM cdb_analysis_catalog WHERE username = '${ctx._username}'
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
var assert = require('assert');
|
||||
var step = require('step');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
var allowQueryParams = require('../middleware/allow-query-params');
|
||||
var vectorError = require('../middleware/vector-error');
|
||||
|
||||
var DataviewBackend = require('../backends/dataview');
|
||||
var AnalysisStatusBackend = require('../backends/analysis-status');
|
||||
|
||||
var MapStoreMapConfigProvider = require('../models/mapconfig/provider/map-store-provider');
|
||||
|
||||
var QueryTables = require('cartodb-query-tables');
|
||||
const cors = require('../middleware/cors');
|
||||
const userMiddleware = require('../middleware/user');
|
||||
const allowQueryParams = require('../middleware/allow-query-params');
|
||||
const vectorError = require('../middleware/vector-error');
|
||||
const DataviewBackend = require('../backends/dataview');
|
||||
const AnalysisStatusBackend = require('../backends/analysis-status');
|
||||
const MapStoreMapConfigProvider = require('../models/mapconfig/provider/map-store-provider');
|
||||
const QueryTables = require('cartodb-query-tables');
|
||||
const SUPPORTED_FORMATS = {
|
||||
grid_json: true,
|
||||
json_torque: true,
|
||||
torque_json: true,
|
||||
png: true,
|
||||
png32: true,
|
||||
mvt: true
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {AuthApi} authApi
|
||||
* @param {prepareContext} prepareContext
|
||||
* @param {PgConnection} pgConnection
|
||||
* @param {MapStore} mapStore
|
||||
* @param {TileBackend} tileBackend
|
||||
@@ -46,64 +48,119 @@ function LayergroupController(prepareContext, pgConnection, mapStore, tileBacken
|
||||
module.exports = LayergroupController;
|
||||
|
||||
LayergroupController.prototype.register = function(app) {
|
||||
const { base_url_mapconfig: basePath } = app;
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/:z/:x/:y@:scale_factor?x.:format',
|
||||
`${basePath}/:token/:z/:x/:y@:scale_factor?x.:format`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
this.prepareContext,
|
||||
this.tile.bind(this),
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
|
||||
getTile(this.tileBackend, 'map_tile'),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
incrementSuccessMetrics(global.statsClient),
|
||||
sendResponse(),
|
||||
incrementErrorMetrics(global.statsClient),
|
||||
tileError(),
|
||||
vectorError()
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/:z/:x/:y.:format',
|
||||
`${basePath}/:token/:z/:x/:y.:format`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
this.prepareContext,
|
||||
this.tile.bind(this),
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
|
||||
getTile(this.tileBackend, 'map_tile'),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
incrementSuccessMetrics(global.statsClient),
|
||||
sendResponse(),
|
||||
incrementErrorMetrics(global.statsClient),
|
||||
tileError(),
|
||||
vectorError()
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/:layer/:z/:x/:y.(:format)',
|
||||
`${basePath}/:token/:layer/:z/:x/:y.(:format)`,
|
||||
distinguishLayergroupFromStaticRoute(),
|
||||
cors(),
|
||||
userMiddleware,
|
||||
validateLayerRouteMiddleware,
|
||||
userMiddleware(),
|
||||
this.prepareContext,
|
||||
this.layer.bind(this),
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
|
||||
getTile(this.tileBackend, 'maplayer_tile'),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
incrementSuccessMetrics(global.statsClient),
|
||||
sendResponse(),
|
||||
incrementErrorMetrics(global.statsClient),
|
||||
tileError(),
|
||||
vectorError()
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/:layer/attributes/:fid',
|
||||
`${basePath}/:token/:layer/attributes/:fid`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
this.prepareContext,
|
||||
this.attributes.bind(this)
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
|
||||
getFeatureAttributes(this.attributesBackend),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
const forcedFormat = 'png';
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/static/center/:token/:z/:lat/:lng/:width/:height.:format',
|
||||
`${basePath}/static/center/:token/:z/:lat/:lng/:width/:height.:format`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
allowQueryParams(['layer']),
|
||||
this.prepareContext,
|
||||
this.center.bind(this)
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi, forcedFormat),
|
||||
getPreviewImageByCenter(this.previewBackend),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format',
|
||||
`${basePath}/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
allowQueryParams(['layer']),
|
||||
this.prepareContext,
|
||||
this.bbox.bind(this)
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi, forcedFormat),
|
||||
getPreviewImageByBoundingBox(this.previewBackend),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
// Undocumented/non-supported API endpoint methods.
|
||||
// Use at your own peril.
|
||||
|
||||
var allowedDataviewQueryParams = [
|
||||
const allowedDataviewQueryParams = [
|
||||
'filters', // json
|
||||
'own_filter', // 0, 1
|
||||
'no_filters', // 0, 1
|
||||
@@ -119,395 +176,465 @@ LayergroupController.prototype.register = function(app) {
|
||||
];
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/dataview/:dataviewName',
|
||||
`${basePath}/:token/dataview/:dataviewName`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
allowQueryParams(allowedDataviewQueryParams),
|
||||
this.prepareContext,
|
||||
this.dataview.bind(this)
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
|
||||
getDataview(this.dataviewBackend),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName',
|
||||
`${basePath}/:token/:layer/widget/:dataviewName`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
allowQueryParams(allowedDataviewQueryParams),
|
||||
this.prepareContext,
|
||||
this.dataview.bind(this)
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
|
||||
getDataview(this.dataviewBackend),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/dataview/:dataviewName/search',
|
||||
`${basePath}/:token/dataview/:dataviewName/search`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
allowQueryParams(allowedDataviewQueryParams),
|
||||
this.prepareContext,
|
||||
this.dataviewSearch.bind(this)
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
|
||||
dataviewSearch(this.dataviewBackend),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName/search',
|
||||
`${basePath}/:token/:layer/widget/:dataviewName/search`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
allowQueryParams(allowedDataviewQueryParams),
|
||||
this.prepareContext,
|
||||
this.dataviewSearch.bind(this)
|
||||
createMapStoreMapConfigProvider(this.mapStore, this.userLimitsApi),
|
||||
dataviewSearch(this.dataviewBackend),
|
||||
setCacheControlHeader(),
|
||||
setLastModifiedHeader(),
|
||||
getAffectedTables(this.layergroupAffectedTables, this.pgConnection, this.mapStore),
|
||||
setCacheChannelHeader(),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/analysis/node/:nodeId',
|
||||
`${basePath}/:token/analysis/node/:nodeId`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
this.prepareContext,
|
||||
this.analysisNodeStatus.bind(this)
|
||||
analysisNodeStatus(this.analysisStatusBackend),
|
||||
sendResponse()
|
||||
);
|
||||
};
|
||||
|
||||
LayergroupController.prototype.analysisNodeStatus = function(req, res, next) {
|
||||
var self = this;
|
||||
function distinguishLayergroupFromStaticRoute () {
|
||||
return function distinguishLayergroupFromStaticRouteMiddleware(req, res, next) {
|
||||
if (req.params.token === 'static') {
|
||||
return next('route');
|
||||
}
|
||||
|
||||
step(
|
||||
function retrieveNodeStatus() {
|
||||
self.analysisStatusBackend.getNodeStatus(res.locals, this);
|
||||
},
|
||||
function finish(err, nodeStatus, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function analysisNodeStatus (analysisStatusBackend) {
|
||||
return function analysisNodeStatusMiddleware(req, res, next) {
|
||||
analysisStatusBackend.getNodeStatus(res.locals, (err, nodeStatus, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'GET NODE STATUS';
|
||||
next(err);
|
||||
} else {
|
||||
self.sendResponse(req, res, nodeStatus, 200, {
|
||||
'Cache-Control': 'public,max-age=5',
|
||||
'Last-Modified': new Date().toUTCString()
|
||||
});
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.set({
|
||||
'Cache-Control': 'public,max-age=5',
|
||||
'Last-Modified': new Date().toUTCString()
|
||||
});
|
||||
|
||||
res.body = nodeStatus;
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getRequestParams(locals) {
|
||||
const params = Object.assign({}, locals);
|
||||
|
||||
delete params.mapConfigProvider;
|
||||
delete params.allowedQueryParams;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function createMapStoreMapConfigProvider (mapStore, userLimitsApi, forcedFormat = null) {
|
||||
return function createMapStoreMapConfigProviderMiddleware (req, res, next) {
|
||||
const { user } = res.locals;
|
||||
|
||||
const params = getRequestParams(res.locals);
|
||||
|
||||
if (forcedFormat) {
|
||||
params.format = forcedFormat;
|
||||
params.layer = params.layer || 'all';
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
LayergroupController.prototype.dataview = function(req, res, next) {
|
||||
var self = this;
|
||||
res.locals.mapConfigProvider = new MapStoreMapConfigProvider(mapStore, user, userLimitsApi, params);
|
||||
|
||||
step(
|
||||
function retrieveDataview() {
|
||||
var mapConfigProvider = new MapStoreMapConfigProvider(
|
||||
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
|
||||
);
|
||||
self.dataviewBackend.getDataview(
|
||||
mapConfigProvider,
|
||||
res.locals.user,
|
||||
res.locals,
|
||||
this
|
||||
);
|
||||
},
|
||||
function finish(err, dataview, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function getDataview (dataviewBackend) {
|
||||
return function getDataviewMiddleware (req, res, next) {
|
||||
const { user, mapConfigProvider } = res.locals;
|
||||
const params = getRequestParams(res.locals);
|
||||
|
||||
dataviewBackend.getDataview(mapConfigProvider, user, params, (err, dataview, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'GET DATAVIEW';
|
||||
next(err);
|
||||
} else {
|
||||
self.sendResponse(req, res, dataview, 200);
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
LayergroupController.prototype.dataviewSearch = function(req, res, next) {
|
||||
var self = this;
|
||||
res.body = dataview;
|
||||
|
||||
step(
|
||||
function searchDataview() {
|
||||
var mapConfigProvider = new MapStoreMapConfigProvider(
|
||||
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
|
||||
);
|
||||
self.dataviewBackend.search(mapConfigProvider, res.locals.user, req.params.dataviewName, res.locals, this);
|
||||
},
|
||||
function finish(err, searchResult, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function dataviewSearch (dataviewBackend) {
|
||||
return function dataviewSearchMiddleware (req, res, next) {
|
||||
const { user, dataviewName, mapConfigProvider } = res.locals;
|
||||
const params = getRequestParams(res.locals);
|
||||
|
||||
dataviewBackend.search(mapConfigProvider, user, dataviewName, params, (err, searchResult, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'GET DATAVIEW SEARCH';
|
||||
next(err);
|
||||
} else {
|
||||
self.sendResponse(req, res, searchResult, 200);
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
res.body = searchResult;
|
||||
|
||||
LayergroupController.prototype.attributes = function(req, res, next) {
|
||||
var self = this;
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
req.profiler.start('windshaft.maplayer_attribute');
|
||||
function getFeatureAttributes (attributesBackend) {
|
||||
return function getFeatureAttributesMiddleware (req, res, next) {
|
||||
req.profiler.start('windshaft.maplayer_attribute');
|
||||
|
||||
step(
|
||||
function retrieveFeatureAttributes() {
|
||||
var mapConfigProvider = new MapStoreMapConfigProvider(
|
||||
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
|
||||
);
|
||||
self.attributesBackend.getFeatureAttributes(mapConfigProvider, res.locals, false, this);
|
||||
},
|
||||
function finish(err, tile, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
const { mapConfigProvider } = res.locals;
|
||||
const params = getRequestParams(res.locals);
|
||||
|
||||
attributesBackend.getFeatureAttributes(mapConfigProvider, params, false, (err, tile, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'GET ATTRIBUTES';
|
||||
next(err);
|
||||
} else {
|
||||
self.sendResponse(req, res, tile, 200);
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
res.body = tile;
|
||||
|
||||
// Gets a tile for a given token and set of tile ZXY coords. (OSM style)
|
||||
LayergroupController.prototype.tile = function(req, res, next) {
|
||||
req.profiler.start('windshaft.map_tile');
|
||||
this.tileOrLayer(req, res, next);
|
||||
};
|
||||
|
||||
// Gets a tile for a given token, layer set of tile ZXY coords. (OSM style)
|
||||
LayergroupController.prototype.layer = function(req, res, next) {
|
||||
req.profiler.start('windshaft.maplayer_tile');
|
||||
this.tileOrLayer(req, res, next);
|
||||
};
|
||||
|
||||
LayergroupController.prototype.tileOrLayer = function (req, res, next) {
|
||||
var self = this;
|
||||
|
||||
step(
|
||||
function mapController$getTileOrGrid() {
|
||||
self.tileBackend.getTile(
|
||||
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
|
||||
req.params, this
|
||||
);
|
||||
},
|
||||
function mapController$finalize(err, tile, headers, stats) {
|
||||
req.profiler.add(stats);
|
||||
self.finalizeGetTileOrGrid(err, req, res, tile, headers, next);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function getStatusCode(tile, format){
|
||||
return tile.length===0 && format==='mvt'? 204:200;
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// This function is meant for being called as the very last
|
||||
// step by all endpoints serving tiles or grids
|
||||
LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers, next) {
|
||||
var supportedFormats = {
|
||||
grid_json: true,
|
||||
json_torque: true,
|
||||
torque_json: true,
|
||||
png: true,
|
||||
png32: true,
|
||||
mvt: true
|
||||
};
|
||||
function getStatusCode(tile, format){
|
||||
return tile.length === 0 && format === 'mvt'? 204 : 200;
|
||||
}
|
||||
|
||||
var formatStat = 'invalid';
|
||||
if (req.params.format) {
|
||||
var format = req.params.format.replace('.', '_');
|
||||
if (supportedFormats[format]) {
|
||||
formatStat = format;
|
||||
}
|
||||
}
|
||||
function parseFormat (format = '') {
|
||||
const prettyFormat = format.replace('.', '_');
|
||||
return SUPPORTED_FORMATS[prettyFormat] ? prettyFormat : 'invalid';
|
||||
}
|
||||
|
||||
if (err) {
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
||||
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
|
||||
function getTile (tileBackend, profileLabel = 'tile') {
|
||||
return function getTileMiddleware (req, res, next) {
|
||||
req.profiler.start(`windshaft.${profileLabel}`);
|
||||
|
||||
// Rewrite mapnik parsing errors to start with layer number
|
||||
var matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
|
||||
if (matches) {
|
||||
errMsg = 'style'+matches[2]+': ' + matches[1];
|
||||
}
|
||||
err.message = errMsg;
|
||||
const { mapConfigProvider } = res.locals;
|
||||
const params = getRequestParams(res.locals);
|
||||
|
||||
err.label = 'TILE RENDER';
|
||||
next(err);
|
||||
|
||||
global.statsClient.increment('windshaft.tiles.error');
|
||||
global.statsClient.increment('windshaft.tiles.' + formatStat + '.error');
|
||||
} else {
|
||||
this.sendResponse(req, res, tile, getStatusCode(tile, formatStat), headers);
|
||||
global.statsClient.increment('windshaft.tiles.success');
|
||||
global.statsClient.increment('windshaft.tiles.' + formatStat + '.success');
|
||||
}
|
||||
};
|
||||
|
||||
LayergroupController.prototype.bbox = function(req, res, next) {
|
||||
this.staticMap(req, res, +req.params.width, +req.params.height, {
|
||||
west: +req.params.west,
|
||||
north: +req.params.north,
|
||||
east: +req.params.east,
|
||||
south: +req.params.south
|
||||
}, null, next);
|
||||
};
|
||||
|
||||
LayergroupController.prototype.center = function(req, res, next) {
|
||||
this.staticMap(req, res, +req.params.width, +req.params.height, +req.params.z, {
|
||||
lng: +req.params.lng,
|
||||
lat: +req.params.lat
|
||||
}, next);
|
||||
};
|
||||
|
||||
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center, next) {
|
||||
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
||||
// We force always the tile to be generated using PNG because
|
||||
// is the only format we support by now
|
||||
res.locals.format = 'png';
|
||||
res.locals.layer = res.locals.layer || 'all';
|
||||
|
||||
var self = this;
|
||||
|
||||
step(
|
||||
function getImage() {
|
||||
if (center) {
|
||||
self.previewBackend.getImage(
|
||||
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
|
||||
format, width, height, zoom, center, this);
|
||||
} else {
|
||||
self.previewBackend.getImage(
|
||||
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
|
||||
format, width, height, zoom /* bounds */, this);
|
||||
}
|
||||
},
|
||||
function handleImage(err, image, headers, stats) {
|
||||
req.profiler.done('render-' + format);
|
||||
req.profiler.add(stats || {});
|
||||
tileBackend.getTile(mapConfigProvider, params, (err, tile, headers, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'STATIC_MAP';
|
||||
next(err);
|
||||
} else {
|
||||
res.set('Content-Type', headers['Content-Type'] || 'image/' + format);
|
||||
self.sendResponse(req, res, image, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
LayergroupController.prototype.sendResponse = function(req, res, body, status, headers) {
|
||||
var self = this;
|
||||
|
||||
req.profiler.done('res');
|
||||
|
||||
res.set('Cache-Control', 'public,max-age=31536000');
|
||||
|
||||
// Set Last-Modified header
|
||||
var lastUpdated;
|
||||
if (res.locals.cache_buster) {
|
||||
// Assuming cache_buster is a timestamp
|
||||
lastUpdated = new Date(parseInt(res.locals.cache_buster));
|
||||
} else {
|
||||
lastUpdated = new Date();
|
||||
}
|
||||
res.set('Last-Modified', lastUpdated.toUTCString());
|
||||
|
||||
var dbName = res.locals.dbname;
|
||||
step(
|
||||
function getAffectedTables() {
|
||||
self.getAffectedTables(res.locals.user, dbName, res.locals.token, this);
|
||||
},
|
||||
function sendResponse(err, affectedTables) {
|
||||
req.profiler.done('affectedTables');
|
||||
if (err) {
|
||||
global.logger.warn('ERROR generating cache channel: ' + err);
|
||||
}
|
||||
if (!!affectedTables) {
|
||||
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
|
||||
self.surrogateKeysCache.tag(res, affectedTables);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
res.set(headers);
|
||||
}
|
||||
|
||||
res.status(status);
|
||||
const formatStat = parseFormat(req.params.format);
|
||||
|
||||
if (!Buffer.isBuffer(body) && typeof body === 'object') {
|
||||
if (req.query && req.query.callback) {
|
||||
res.jsonp(body);
|
||||
} else {
|
||||
res.json(body);
|
||||
}
|
||||
} else {
|
||||
res.send(body);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
res.statusCode = getStatusCode(tile, formatStat);
|
||||
res.body = tile;
|
||||
|
||||
LayergroupController.prototype.getAffectedTables = function(user, dbName, layergroupId, callback) {
|
||||
|
||||
if (this.layergroupAffectedTables.hasAffectedTables(dbName, layergroupId)) {
|
||||
return callback(null, this.layergroupAffectedTables.get(dbName, layergroupId));
|
||||
}
|
||||
|
||||
var self = this;
|
||||
step(
|
||||
function extractSQL() {
|
||||
step(
|
||||
function loadFromStore() {
|
||||
self.mapStore.load(layergroupId, this);
|
||||
},
|
||||
function getSQL(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
|
||||
var queries = [];
|
||||
mapConfig.getLayers().forEach(function(layer) {
|
||||
queries.push(layer.options.sql);
|
||||
if (layer.options.affected_tables) {
|
||||
layer.options.affected_tables.map(function(table) {
|
||||
queries.push('SELECT * FROM ' + table + ' LIMIT 0');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return queries.length ? queries.join(';') : null;
|
||||
},
|
||||
this
|
||||
);
|
||||
},
|
||||
function findAffectedTables(err, sql) {
|
||||
assert.ifError(err);
|
||||
|
||||
if ( ! sql ) {
|
||||
throw new Error("this request doesn't need an X-Cache-Channel generated");
|
||||
}
|
||||
|
||||
step(
|
||||
function getConnection() {
|
||||
self.pgConnection.getConnection(user, this);
|
||||
},
|
||||
function getAffectedTables(err, connection) {
|
||||
assert.ifError(err);
|
||||
|
||||
QueryTables.getAffectedTablesFromQuery(connection, sql, this);
|
||||
},
|
||||
this
|
||||
);
|
||||
},
|
||||
function buildCacheChannel(err, tables) {
|
||||
assert.ifError(err);
|
||||
self.layergroupAffectedTables.set(dbName, layergroupId, tables);
|
||||
|
||||
return tables;
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
function validateLayerRouteMiddleware(req, res, next) {
|
||||
if (req.params.token === 'static') {
|
||||
return next('route');
|
||||
}
|
||||
|
||||
next();
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getPreviewImageByCenter (previewBackend) {
|
||||
return function getPreviewImageByCenterMiddleware (req, res, next) {
|
||||
const width = +req.params.width;
|
||||
const height = +req.params.height;
|
||||
const zoom = +req.params.z;
|
||||
const center = {
|
||||
lng: +req.params.lng,
|
||||
lat: +req.params.lat
|
||||
};
|
||||
|
||||
const format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
||||
const { mapConfigProvider: provider } = res.locals;
|
||||
|
||||
previewBackend.getImage(provider, format, width, height, zoom, center, (err, image, headers, stats = {}) => {
|
||||
req.profiler.done(`render-${format}`);
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'STATIC_MAP';
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
res.set(headers);
|
||||
}
|
||||
|
||||
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
|
||||
|
||||
res.body = image;
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getPreviewImageByBoundingBox (previewBackend) {
|
||||
return function getPreviewImageByBoundingBoxMiddleware (req, res, next) {
|
||||
const width = +req.params.width;
|
||||
const height = +req.params.height;
|
||||
const bounds = {
|
||||
west: +req.params.west,
|
||||
north: +req.params.north,
|
||||
east: +req.params.east,
|
||||
south: +req.params.south
|
||||
};
|
||||
const format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
||||
const { mapConfigProvider: provider } = res.locals;
|
||||
|
||||
previewBackend.getImage(provider, format, width, height, bounds, (err, image, headers, stats = {}) => {
|
||||
req.profiler.done(`render-${format}`);
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'STATIC_MAP';
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
res.set(headers);
|
||||
}
|
||||
|
||||
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
|
||||
|
||||
res.body = image;
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function setLastModifiedHeader () {
|
||||
return function setLastModifiedHeaderMiddleware (req, res, next) {
|
||||
let { cache_buster: cacheBuster } = res.locals;
|
||||
|
||||
cacheBuster = parseInt(cacheBuster, 10);
|
||||
|
||||
const lastUpdated = res.locals.cache_buster ? new Date(cacheBuster) : new Date();
|
||||
|
||||
res.set('Last-Modified', lastUpdated.toUTCString());
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function setCacheControlHeader () {
|
||||
return function setCacheControlHeaderMiddleware (req, res, next) {
|
||||
res.set('Cache-Control', 'public,max-age=31536000');
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function getAffectedTables (layergroupAffectedTables, pgConnection, mapStore) {
|
||||
return function getAffectedTablesMiddleware (req, res, next) {
|
||||
const { user, dbname, token } = res.locals;
|
||||
|
||||
if (layergroupAffectedTables.hasAffectedTables(dbname, token)) {
|
||||
res.locals.affectedTables = layergroupAffectedTables.get(dbname, token);
|
||||
return next();
|
||||
}
|
||||
|
||||
mapStore.load(token, (err, mapconfig) => {
|
||||
if (err) {
|
||||
global.logger.warn('ERROR generating cache channel:', err);
|
||||
return next();
|
||||
}
|
||||
|
||||
const queries = [];
|
||||
mapconfig.getLayers().forEach(function(layer) {
|
||||
queries.push(layer.options.sql);
|
||||
if (layer.options.affected_tables) {
|
||||
layer.options.affected_tables.map(function(table) {
|
||||
queries.push(`SELECT * FROM ${table} LIMIT 0`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const sql = queries.length ? queries.join(';') : null;
|
||||
|
||||
if (!sql) {
|
||||
global.logger.warn('ERROR generating cache channel:' +
|
||||
' this request doesn\'t need an X-Cache-Channel generated');
|
||||
return next();
|
||||
}
|
||||
|
||||
pgConnection.getConnection(user, (err, connection) => {
|
||||
if (err) {
|
||||
global.logger.warn('ERROR generating cache channel:', err);
|
||||
return next();
|
||||
}
|
||||
|
||||
QueryTables.getAffectedTablesFromQuery(connection, sql, (err, affectedTables) => {
|
||||
req.profiler.done('getAffectedTablesFromQuery');
|
||||
if (err) {
|
||||
global.logger.warn('ERROR generating cache channel: ', err);
|
||||
return next();
|
||||
}
|
||||
|
||||
// feed affected tables cache so it can be reused from, for instance, map controller
|
||||
layergroupAffectedTables.set(dbname, token, affectedTables);
|
||||
|
||||
res.locals.affectedTables = affectedTables;
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function setCacheChannelHeader () {
|
||||
return function setCacheChannelHeaderMiddleware (req, res, next) {
|
||||
const { affectedTables } = res.locals;
|
||||
|
||||
if (affectedTables) {
|
||||
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function setSurrogateKeyHeader (surrogateKeysCache) {
|
||||
return function setSurrogateKeyHeaderMiddleware (req, res, next) {
|
||||
const { affectedTables } = res.locals;
|
||||
|
||||
if (affectedTables) {
|
||||
surrogateKeysCache.tag(res, affectedTables);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function incrementSuccessMetrics (statsClient) {
|
||||
return function incrementSuccessMetricsMiddleware (req, res, next) {
|
||||
const formatStat = parseFormat(req.params.format);
|
||||
|
||||
statsClient.increment('windshaft.tiles.success');
|
||||
statsClient.increment(`windshaft.tiles.${formatStat}.success`);
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function sendResponse () {
|
||||
return function sendResponseMiddleware (req, res) {
|
||||
req.profiler.done('res');
|
||||
|
||||
res.status(res.statusCode || 200);
|
||||
|
||||
if (!Buffer.isBuffer(res.body) && typeof res.body === 'object') {
|
||||
if (req.query && req.query.callback) {
|
||||
res.jsonp(res.body);
|
||||
} else {
|
||||
res.json(res.body);
|
||||
}
|
||||
} else {
|
||||
res.send(res.body);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function incrementErrorMetrics (statsClient) {
|
||||
return function incrementErrorMetricsMiddleware (err, req, res, next) {
|
||||
const formatStat = parseFormat(req.params.format);
|
||||
|
||||
statsClient.increment('windshaft.tiles.error');
|
||||
statsClient.increment(`windshaft.tiles.${formatStat}.error`);
|
||||
|
||||
next(err);
|
||||
};
|
||||
}
|
||||
|
||||
function tileError () {
|
||||
return function tileErrorMiddleware (err, req, res, next) {
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
||||
let errMsg = err.message ? ( '' + err.message ) : ( '' + err );
|
||||
|
||||
// Rewrite mapnik parsing errors to start with layer number
|
||||
const matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
|
||||
|
||||
if (matches) {
|
||||
errMsg = `style${matches[2]}: ${matches[1]}`;
|
||||
}
|
||||
|
||||
err.message = errMsg;
|
||||
err.label = 'TILE RENDER';
|
||||
|
||||
next(err);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
var _ = require('underscore');
|
||||
var windshaft = require('windshaft');
|
||||
var QueryTables = require('cartodb-query-tables');
|
||||
|
||||
var ResourceLocator = require('../models/resource-locator');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
const _ = require('underscore');
|
||||
const windshaft = require('windshaft');
|
||||
const MapConfig = windshaft.model.MapConfig;
|
||||
const Datasource = windshaft.model.Datasource;
|
||||
const QueryTables = require('cartodb-query-tables');
|
||||
const ResourceLocator = require('../models/resource-locator');
|
||||
const cors = require('../middleware/cors');
|
||||
const userMiddleware = require('../middleware/user');
|
||||
const allowQueryParams = require('../middleware/allow-query-params');
|
||||
|
||||
var MapConfig = windshaft.model.MapConfig;
|
||||
var Datasource = windshaft.model.Datasource;
|
||||
|
||||
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
|
||||
|
||||
var NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider');
|
||||
var CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/create-layergroup-provider');
|
||||
const NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
|
||||
const NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider');
|
||||
const CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/create-layergroup-provider');
|
||||
const LayergroupMetadata = require('../utils/layergroup-metadata');
|
||||
|
||||
/**
|
||||
* @param {AuthApi} authApi
|
||||
@@ -41,7 +37,8 @@ function MapController(prepareContext, pgConnection, templateMaps, mapBackend, m
|
||||
this.layergroupAffectedTables = layergroupAffectedTables;
|
||||
|
||||
this.mapConfigAdapter = mapConfigAdapter;
|
||||
this.resourceLocator = new ResourceLocator(global.environment);
|
||||
const resourceLocator = new ResourceLocator(global.environment);
|
||||
this.layergroupMetadata = new LayergroupMetadata(resourceLocator);
|
||||
|
||||
this.statsBackend = statsBackend;
|
||||
this.prepareContext = prepareContext;
|
||||
@@ -69,35 +66,55 @@ MapController.prototype.composeCreateMapMiddleware = function (useTemplate = fal
|
||||
|
||||
return [
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
allowQueryParams(['aggregation']),
|
||||
this.prepareContext,
|
||||
this.initProfiler(isTemplateInstantiation),
|
||||
this.checkJsonContentType(),
|
||||
useTemplate ? this.checkInstantiteLayergroup() : this.checkCreateLayergroup(),
|
||||
useTemplate ? this.getTemplate() : this.prepareAdapterMapConfig(),
|
||||
useTemplate ? this.instantiateLayergroup() : this.createLayergroup(),
|
||||
this.incrementMapViewCount(),
|
||||
this.augmentLayergroupData(),
|
||||
this.getAffectedTables(),
|
||||
this.setCacheChannel(),
|
||||
this.setLastModified(),
|
||||
this.setLastUpdatedTimeToLayergroup(),
|
||||
this.setCacheControl(),
|
||||
this.setLayerStats(),
|
||||
this.setLayergroupIdHeader(useTemplateHash),
|
||||
this.setDataviewsAndWidgetsUrlsToLayergroupMetadata(),
|
||||
this.setAnalysesMetadataToLayergroup(includeQuery),
|
||||
this.setTurboCartoMetadataToLayergroup(),
|
||||
this.setAggregationMetadataToLayergroup(),
|
||||
this.setTilejsonMetadataToLayergroup(),
|
||||
this.setSurrogateKeyHeader(),
|
||||
this.sendResponse(),
|
||||
this.augmentError({ label, addContext })
|
||||
initProfiler(isTemplateInstantiation),
|
||||
checkJsonContentType(),
|
||||
this.getCreateMapMiddlewares(useTemplate),
|
||||
incrementMapViewCount(this.metadataBackend),
|
||||
augmentLayergroupData(),
|
||||
getAffectedTables(this.pgConnection, this.layergroupAffectedTables),
|
||||
setCacheChannel(),
|
||||
setLastModified(),
|
||||
setLastUpdatedTimeToLayergroup(),
|
||||
setCacheControl(),
|
||||
setLayerStats(this.pgConnection, this.statsBackend),
|
||||
setLayergroupIdHeader(this.templateMaps ,useTemplateHash),
|
||||
setDataviewsAndWidgetsUrlsToLayergroupMetadata(this.layergroupMetadata),
|
||||
setAnalysesMetadataToLayergroup(this.layergroupMetadata, includeQuery),
|
||||
setTurboCartoMetadataToLayergroup(this.layergroupMetadata),
|
||||
setAggregationMetadataToLayergroup(this.layergroupMetadata),
|
||||
setTilejsonMetadataToLayergroup(this.layergroupMetadata),
|
||||
setSurrogateKeyHeader(this.surrogateKeysCache),
|
||||
sendResponse(),
|
||||
augmentError({ label, addContext })
|
||||
];
|
||||
};
|
||||
|
||||
MapController.prototype.initProfiler = function (isTemplateInstantiation) {
|
||||
MapController.prototype.getCreateMapMiddlewares = function (useTemplate) {
|
||||
if (useTemplate) {
|
||||
return [
|
||||
checkInstantiteLayergroup(),
|
||||
getTemplate(
|
||||
this.templateMaps,
|
||||
this.pgConnection,
|
||||
this.metadataBackend,
|
||||
this.userLimitsApi,
|
||||
this.mapConfigAdapter
|
||||
),
|
||||
instantiateLayergroup(this.mapBackend, this.userLimitsApi)
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
checkCreateLayergroup(),
|
||||
prepareAdapterMapConfig(this.mapConfigAdapter),
|
||||
createLayergroup (this.mapBackend, this.userLimitsApi)
|
||||
];
|
||||
};
|
||||
|
||||
function initProfiler (isTemplateInstantiation) {
|
||||
const operation = isTemplateInstantiation ? 'instance_template' : 'createmap';
|
||||
|
||||
return function initProfilerMiddleware (req, res, next) {
|
||||
@@ -105,9 +122,9 @@ MapController.prototype.initProfiler = function (isTemplateInstantiation) {
|
||||
req.profiler.done(`${operation}.initProfilerMiddleware`);
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.checkJsonContentType = function () {
|
||||
function checkJsonContentType () {
|
||||
return function checkJsonContentTypeMiddleware(req, res, next) {
|
||||
if (req.method === 'POST' && !req.is('application/json')) {
|
||||
return next(new Error('POST data must be of type application/json'));
|
||||
@@ -117,9 +134,9 @@ MapController.prototype.checkJsonContentType = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.checkInstantiteLayergroup = function () {
|
||||
function checkInstantiteLayergroup () {
|
||||
return function checkInstantiteLayergroupMiddleware(req, res, next) {
|
||||
if (req.method === 'GET') {
|
||||
const { callback, config } = req.query;
|
||||
@@ -141,9 +158,9 @@ MapController.prototype.checkInstantiteLayergroup = function () {
|
||||
|
||||
return next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.checkCreateLayergroup = function () {
|
||||
function checkCreateLayergroup () {
|
||||
return function checkCreateLayergroupMiddleware (req, res, next) {
|
||||
if (req.method === 'GET') {
|
||||
const { config } = res.locals;
|
||||
@@ -162,19 +179,19 @@ MapController.prototype.checkCreateLayergroup = function () {
|
||||
req.profiler.done('checkCreateLayergroup');
|
||||
return next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.getTemplate = function () {
|
||||
function getTemplate (templateMaps, pgConnection, metadataBackend, userLimitsApi, mapConfigAdapter) {
|
||||
return function getTemplateMiddleware (req, res, next) {
|
||||
const templateParams = req.body;
|
||||
const { user } = res.locals;
|
||||
|
||||
const mapconfigProvider = new NamedMapMapConfigProvider(
|
||||
this.templateMaps,
|
||||
this.pgConnection,
|
||||
this.metadataBackend,
|
||||
this.userLimitsApi,
|
||||
this.mapConfigAdapter,
|
||||
templateMaps,
|
||||
pgConnection,
|
||||
metadataBackend,
|
||||
userLimitsApi,
|
||||
mapConfigAdapter,
|
||||
user,
|
||||
req.params.template_id,
|
||||
templateParams,
|
||||
@@ -194,10 +211,10 @@ MapController.prototype.getTemplate = function () {
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.prepareAdapterMapConfig = function () {
|
||||
function prepareAdapterMapConfig (mapConfigAdapter) {
|
||||
return function prepareAdapterMapConfigMiddleware(req, res, next) {
|
||||
const requestMapConfig = req.body;
|
||||
const { user, dbhost, dbport, dbname, dbuser, dbpassword, api_key } = res.locals;
|
||||
@@ -219,7 +236,7 @@ MapController.prototype.prepareAdapterMapConfig = function () {
|
||||
}
|
||||
};
|
||||
|
||||
this.mapConfigAdapter.getMapConfig(user, requestMapConfig, res.locals, context, (err, requestMapConfig) => {
|
||||
mapConfigAdapter.getMapConfig(user, requestMapConfig, res.locals, context, (err, requestMapConfig) => {
|
||||
req.profiler.done('anonymous.getMapConfig');
|
||||
if (err) {
|
||||
return next(err);
|
||||
@@ -230,22 +247,22 @@ MapController.prototype.prepareAdapterMapConfig = function () {
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.createLayergroup = function () {
|
||||
function createLayergroup (mapBackend, userLimitsApi) {
|
||||
return function createLayergroupMiddleware (req, res, next) {
|
||||
const requestMapConfig = req.body;
|
||||
const { context, user } = res.locals;
|
||||
const datasource = context.datasource || Datasource.EmptyDatasource();
|
||||
const mapconfig = new MapConfig(requestMapConfig, datasource);
|
||||
const mapconfigProvider =
|
||||
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, res.locals);
|
||||
new CreateLayergroupMapConfigProvider(mapconfig, user, userLimitsApi, res.locals);
|
||||
|
||||
res.locals.mapconfig = mapconfig;
|
||||
res.locals.analysesResults = context.analysesResults;
|
||||
|
||||
this.mapBackend.createLayergroup(mapconfig, res.locals, mapconfigProvider, (err, layergroup) => {
|
||||
mapBackend.createLayergroup(mapconfig, res.locals, mapconfigProvider, (err, layergroup) => {
|
||||
req.profiler.done('createLayergroup');
|
||||
if (err) {
|
||||
return next(err);
|
||||
@@ -255,16 +272,16 @@ MapController.prototype.createLayergroup = function () {
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.instantiateLayergroup = function () {
|
||||
function instantiateLayergroup (mapBackend, userLimitsApi) {
|
||||
return function instantiateLayergroupMiddleware (req, res, next) {
|
||||
const { user, mapconfig, rendererParams } = res.locals;
|
||||
const mapconfigProvider =
|
||||
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, rendererParams);
|
||||
new CreateLayergroupMapConfigProvider(mapconfig, user, userLimitsApi, rendererParams);
|
||||
|
||||
this.mapBackend.createLayergroup(mapconfig, rendererParams, mapconfigProvider, (err, layergroup) => {
|
||||
mapBackend.createLayergroup(mapconfig, rendererParams, mapconfigProvider, (err, layergroup) => {
|
||||
req.profiler.done('createLayergroup');
|
||||
if (err) {
|
||||
return next(err);
|
||||
@@ -281,15 +298,15 @@ MapController.prototype.instantiateLayergroup = function () {
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.incrementMapViewCount = function () {
|
||||
function incrementMapViewCount (metadataBackend) {
|
||||
return function incrementMapViewCountMiddleware(req, res, next) {
|
||||
const { mapconfig, user } = res.locals;
|
||||
|
||||
// Error won't blow up, just be logged.
|
||||
this.metadataBackend.incMapviewCount(user, mapconfig.obj().stat_tag, (err) => {
|
||||
metadataBackend.incMapviewCount(user, mapconfig.obj().stat_tag, (err) => {
|
||||
req.profiler.done('incMapviewCount');
|
||||
|
||||
if (err) {
|
||||
@@ -298,10 +315,10 @@ MapController.prototype.incrementMapViewCount = function () {
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.augmentLayergroupData = function () {
|
||||
function augmentLayergroupData () {
|
||||
return function augmentLayergroupDataMiddleware (req, res, next) {
|
||||
const { layergroup } = res.locals;
|
||||
|
||||
@@ -312,91 +329,13 @@ MapController.prototype.augmentLayergroupData = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
function getTemplateUrl(url) {
|
||||
return url.https || url.http;
|
||||
}
|
||||
|
||||
function getTilejson(tiles, grids) {
|
||||
const tilejson = {
|
||||
tilejson: '2.2.0',
|
||||
tiles: tiles.https || tiles.http
|
||||
};
|
||||
|
||||
if (grids) {
|
||||
tilejson.grids = grids.https || grids.http;
|
||||
}
|
||||
|
||||
return tilejson;
|
||||
}
|
||||
|
||||
MapController.prototype.setTilejsonMetadataToLayergroup = function () {
|
||||
return function augmentLayergroupTilejsonMiddleware (req, res, next) {
|
||||
const { layergroup, user, mapconfig } = res.locals;
|
||||
|
||||
const isVectorOnlyMapConfig = mapconfig.isVectorOnlyMapConfig();
|
||||
let hasMapnikLayers = false;
|
||||
layergroup.metadata.layers.forEach((layerMetadata, index) => {
|
||||
const layerId = mapconfig.getLayerId(index);
|
||||
const rasterResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.png`;
|
||||
if (mapconfig.layerType(index) === 'mapnik') {
|
||||
hasMapnikLayers = true;
|
||||
const vectorResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.mvt`;
|
||||
const layerTilejson = {
|
||||
vector: getTilejson(this.resourceLocator.getTileUrls(user, vectorResource))
|
||||
};
|
||||
if (!isVectorOnlyMapConfig) {
|
||||
let grids = null;
|
||||
const layer = mapconfig.getLayer(index);
|
||||
if (layer.options.interactivity) {
|
||||
const gridResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.grid.json`;
|
||||
grids = this.resourceLocator.getTileUrls(user, gridResource);
|
||||
}
|
||||
layerTilejson.raster = getTilejson(
|
||||
this.resourceLocator.getTileUrls(user, rasterResource),
|
||||
grids
|
||||
);
|
||||
}
|
||||
layerMetadata.tilejson = layerTilejson;
|
||||
} else {
|
||||
layerMetadata.tilejson = {
|
||||
raster: getTilejson(this.resourceLocator.getTileUrls(user, rasterResource))
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const tilejson = {};
|
||||
const url = {};
|
||||
|
||||
if (hasMapnikLayers) {
|
||||
const vectorResource = `${layergroup.layergroupid}/{z}/{x}/{y}.mvt`;
|
||||
tilejson.vector = getTilejson(
|
||||
this.resourceLocator.getTileUrls(user, vectorResource)
|
||||
);
|
||||
url.vector = getTemplateUrl(this.resourceLocator.getTemplateUrls(user, vectorResource));
|
||||
|
||||
if (!isVectorOnlyMapConfig) {
|
||||
const rasterResource = `${layergroup.layergroupid}/{z}/{x}/{y}.png`;
|
||||
tilejson.raster = getTilejson(
|
||||
this.resourceLocator.getTileUrls(user, rasterResource)
|
||||
);
|
||||
url.raster = getTemplateUrl(this.resourceLocator.getTemplateUrls(user, rasterResource));
|
||||
}
|
||||
}
|
||||
|
||||
layergroup.metadata.tilejson = tilejson;
|
||||
layergroup.metadata.url = url;
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.getAffectedTables = function () {
|
||||
function getAffectedTables (pgConnection, layergroupAffectedTables) {
|
||||
return function getAffectedTablesMiddleware (req, res, next) {
|
||||
const { dbname, layergroup, user, mapconfig } = res.locals;
|
||||
|
||||
this.pgConnection.getConnection(user, (err, connection) => {
|
||||
pgConnection.getConnection(user, (err, connection) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -418,17 +357,17 @@ MapController.prototype.getAffectedTables = function () {
|
||||
}
|
||||
|
||||
// feed affected tables cache so it can be reused from, for instance, layergroup controller
|
||||
this.layergroupAffectedTables.set(dbname, layergroup.layergroupId, affectedTables);
|
||||
layergroupAffectedTables.set(dbname, layergroup.layergroupId, affectedTables);
|
||||
|
||||
res.locals.affectedTables = affectedTables;
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.setCacheChannel = function () {
|
||||
function setCacheChannel () {
|
||||
return function setCacheChannelMiddleware (req, res, next) {
|
||||
const { affectedTables } = res.locals;
|
||||
|
||||
@@ -438,9 +377,9 @@ MapController.prototype.setCacheChannel = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.setLastModified = function () {
|
||||
function setLastModified () {
|
||||
return function setLastModifiedMiddleware (req, res, next) {
|
||||
if (req.method === 'GET') {
|
||||
res.set('Last-Modified', (new Date()).toUTCString());
|
||||
@@ -448,9 +387,9 @@ MapController.prototype.setLastModified = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.setLastUpdatedTimeToLayergroup = function () {
|
||||
function setLastUpdatedTimeToLayergroup () {
|
||||
return function setLastUpdatedTimeToLayergroupMiddleware (req, res, next) {
|
||||
const { affectedTables, layergroup, analysesResults } = res.locals;
|
||||
|
||||
@@ -464,7 +403,7 @@ MapController.prototype.setLastUpdatedTimeToLayergroup = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function getLastUpdatedTime(analysesResults, lastUpdateTime) {
|
||||
if (!Array.isArray(analysesResults)) {
|
||||
@@ -479,7 +418,7 @@ function getLastUpdatedTime(analysesResults, lastUpdateTime) {
|
||||
}, lastUpdateTime);
|
||||
}
|
||||
|
||||
MapController.prototype.setCacheControl = function () {
|
||||
function setCacheControl () {
|
||||
return function setCacheControlMiddleware (req, res, next) {
|
||||
if (req.method === 'GET') {
|
||||
var ttl = global.environment.varnish.layergroupTtl || 86400;
|
||||
@@ -488,18 +427,18 @@ MapController.prototype.setCacheControl = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.setLayerStats = function () {
|
||||
function setLayerStats (pgConnection, statsBackend) {
|
||||
return function setLayerStatsMiddleware(req, res, next) {
|
||||
const { user, mapconfig, layergroup } = res.locals;
|
||||
|
||||
this.pgConnection.getConnection(user, (err, connection) => {
|
||||
pgConnection.getConnection(user, (err, connection) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
this.statsBackend.getStats(mapconfig, connection, function(err, layersStats) {
|
||||
statsBackend.getStats(mapconfig, connection, function(err, layersStats) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -513,171 +452,91 @@ MapController.prototype.setLayerStats = function () {
|
||||
next();
|
||||
});
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.setLayergroupIdHeader = function (useTemplateHash) {
|
||||
function setLayergroupIdHeader (templateMaps, useTemplateHash) {
|
||||
return function setLayergroupIdHeaderMiddleware (req, res, next) {
|
||||
const { layergroup, user, template } = res.locals;
|
||||
|
||||
if (useTemplateHash) {
|
||||
var templateHash = this.templateMaps.fingerPrint(template).substring(0, 8);
|
||||
var templateHash = templateMaps.fingerPrint(template).substring(0, 8);
|
||||
layergroup.layergroupid = `${user}@${templateHash}@${layergroup.layergroupid}`;
|
||||
}
|
||||
|
||||
res.set('X-Layergroup-Id', layergroup.layergroupid);
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.setDataviewsAndWidgetsUrlsToLayergroupMetadata = function () {
|
||||
function setDataviewsAndWidgetsUrlsToLayergroupMetadata (layergroupMetadata) {
|
||||
return function setDataviewsAndWidgetsUrlsToLayergroupMetadataMiddleware (req, res, next) {
|
||||
const { layergroup, user, mapconfig } = res.locals;
|
||||
|
||||
this.addDataviewsAndWidgetsUrls(user, layergroup, mapconfig.obj());
|
||||
layergroupMetadata.addDataviewsAndWidgetsUrls(user, layergroup, mapconfig.obj());
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// TODO this should take into account several URL patterns
|
||||
MapController.prototype.addDataviewsAndWidgetsUrls = function(username, layergroup, mapConfig) {
|
||||
this.addDataviewsUrls(username, layergroup, mapConfig);
|
||||
this.addWidgetsUrl(username, layergroup, mapConfig);
|
||||
};
|
||||
|
||||
MapController.prototype.addDataviewsUrls = function(username, layergroup, mapConfig) {
|
||||
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
|
||||
var dataviews = mapConfig.dataviews || {};
|
||||
|
||||
Object.keys(dataviews).forEach(function(dataviewName) {
|
||||
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
|
||||
layergroup.metadata.dataviews[dataviewName] = {
|
||||
url: this.resourceLocator.getUrls(username, resource)
|
||||
};
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
MapController.prototype.addWidgetsUrl = function(username, layergroup, mapConfig) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
|
||||
var mapConfigLayer = mapConfig.layers[layerIndex];
|
||||
if (mapConfigLayer.options && mapConfigLayer.options.widgets) {
|
||||
layer.widgets = layer.widgets || {};
|
||||
Object.keys(mapConfigLayer.options.widgets).forEach(function(widgetName) {
|
||||
var resource = layergroup.layergroupid + '/' + layerIndex + '/widget/' + widgetName;
|
||||
layer.widgets[widgetName] = {
|
||||
type: mapConfigLayer.options.widgets[widgetName].type,
|
||||
url: this.resourceLocator.getUrls(username, resource)
|
||||
};
|
||||
}.bind(this));
|
||||
}
|
||||
return layer;
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
MapController.prototype.setAnalysesMetadataToLayergroup = function (includeQuery) {
|
||||
function setAnalysesMetadataToLayergroup (layergroupMetadata, includeQuery) {
|
||||
return function setAnalysesMetadataToLayergroupMiddleware (req, res, next) {
|
||||
const { layergroup, user, analysesResults = [] } = res.locals;
|
||||
|
||||
this.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery);
|
||||
layergroupMetadata.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery);
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.addAnalysesMetadata = function(username, layergroup, analysesResults, includeQuery) {
|
||||
includeQuery = includeQuery || false;
|
||||
analysesResults = analysesResults || [];
|
||||
layergroup.metadata.analyses = [];
|
||||
|
||||
analysesResults.forEach(function(analysis) {
|
||||
var nodes = analysis.getNodes();
|
||||
layergroup.metadata.analyses.push({
|
||||
nodes: nodes.reduce(function(nodesIdMap, node) {
|
||||
if (node.params.id) {
|
||||
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
|
||||
var nodeRepr = {
|
||||
status: node.getStatus(),
|
||||
url: this.resourceLocator.getUrls(username, nodeResource)
|
||||
};
|
||||
if (includeQuery) {
|
||||
nodeRepr.query = node.getQuery();
|
||||
}
|
||||
if (node.getStatus() === 'failed') {
|
||||
nodeRepr.error_message = node.getErrorMessage();
|
||||
}
|
||||
nodesIdMap[node.params.id] = nodeRepr;
|
||||
}
|
||||
|
||||
return nodesIdMap;
|
||||
}.bind(this), {})
|
||||
});
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
MapController.prototype.setTurboCartoMetadataToLayergroup = function () {
|
||||
function setTurboCartoMetadataToLayergroup (layergroupMetadata) {
|
||||
return function setTurboCartoMetadataToLayergroupMiddleware (req, res, next) {
|
||||
const { layergroup, mapconfig, context } = res.locals;
|
||||
|
||||
addTurboCartoContextMetadata(layergroup, mapconfig.obj(), context);
|
||||
layergroupMetadata.addTurboCartoContextMetadata(layergroup, mapconfig.obj(), context);
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
function addTurboCartoContextMetadata(layergroup, mapConfig, context) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
|
||||
if (context.turboCarto && Array.isArray(context.turboCarto.layers)) {
|
||||
layer.meta.cartocss_meta = context.turboCarto.layers[layerIndex];
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: see how evolve this function, it's a good candidate to be refactored
|
||||
MapController.prototype.setAggregationMetadataToLayergroup = function () {
|
||||
function setAggregationMetadataToLayergroup (layergroupMetadata) {
|
||||
return function setAggregationMetadataToLayergroupMiddleware (req, res, next) {
|
||||
const { layergroup, mapconfig, context } = res.locals;
|
||||
|
||||
addAggregationContextMetadata(layergroup, mapconfig.obj(), context);
|
||||
layergroupMetadata.addAggregationContextMetadata(layergroup, mapconfig.obj(), context);
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
function addAggregationContextMetadata(layergroup, mapConfig, context) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
|
||||
if (context.aggregation && Array.isArray(context.aggregation.layers)) {
|
||||
layer.meta.aggregation = context.aggregation.layers[layerIndex];
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MapController.prototype.setSurrogateKeyHeader = function () {
|
||||
function setTilejsonMetadataToLayergroup (layergroupMetadata) {
|
||||
return function augmentLayergroupTilejsonMiddleware (req, res, next) {
|
||||
const { layergroup, user, mapconfig } = res.locals;
|
||||
|
||||
layergroupMetadata.addTileJsonMetadata(layergroup, user, mapconfig);
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function setSurrogateKeyHeader (surrogateKeysCache) {
|
||||
return function setSurrogateKeyHeaderMiddleware(req, res, next) {
|
||||
const { affectedTables, user, templateName } = res.locals;
|
||||
|
||||
if (req.method === 'GET' && affectedTables.tables && affectedTables.tables.length > 0) {
|
||||
this.surrogateKeysCache.tag(res, affectedTables);
|
||||
surrogateKeysCache.tag(res, affectedTables);
|
||||
}
|
||||
|
||||
if (templateName) {
|
||||
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, templateName));
|
||||
surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, templateName));
|
||||
}
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.sendResponse = function () {
|
||||
function sendResponse () {
|
||||
return function sendResponseMiddleware (req, res) {
|
||||
req.profiler.done('res');
|
||||
const { layergroup } = res.locals;
|
||||
@@ -690,9 +549,9 @@ MapController.prototype.sendResponse = function () {
|
||||
res.json(layergroup);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
MapController.prototype.augmentError = function (options) {
|
||||
function augmentError (options) {
|
||||
const { addContext = false, label = 'MAPS CONTROLLER' } = options;
|
||||
|
||||
return function augmentErrorMiddleware (err, req, res, next) {
|
||||
@@ -707,7 +566,7 @@ MapController.prototype.augmentError = function (options) {
|
||||
|
||||
next(err);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function populateError(err, mapConfig) {
|
||||
var error = new Error(err.message);
|
||||
|
||||
@@ -41,59 +41,73 @@ function NamedMapsController(prepareContext, namedMapProviderCache, tileBackend,
|
||||
module.exports = NamedMapsController;
|
||||
|
||||
NamedMapsController.prototype.register = function(app) {
|
||||
const { base_url_mapconfig, base_url_templated } = app;
|
||||
|
||||
app.get(
|
||||
app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)',
|
||||
`${base_url_templated}/:template_id/:layer/:z/:x/:y.(:format)`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
this.prepareContext,
|
||||
this.getNamedMapProvider('NAMED_MAP_TILE'),
|
||||
this.getAffectedTables(),
|
||||
this.getTile('NAMED_MAP_TILE'),
|
||||
this.setSurrogateKey(),
|
||||
this.setCacheChannelHeader(),
|
||||
this.setLastModifiedHeader(),
|
||||
this.setCacheControlHeader(),
|
||||
this.setContentTypeHeader(),
|
||||
this.respond(),
|
||||
getNamedMapProvider({
|
||||
namedMapProviderCache: this.namedMapProviderCache,
|
||||
label: 'NAMED_MAP_TILE'
|
||||
}),
|
||||
getAffectedTables(),
|
||||
getTile({
|
||||
tileBackend: this.tileBackend,
|
||||
label: 'NAMED_MAP_TILE'
|
||||
}),
|
||||
setSurrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
setCacheChannelHeader(),
|
||||
setLastModifiedHeader(),
|
||||
setCacheControlHeader(),
|
||||
setContentTypeHeader(),
|
||||
sendResponse(),
|
||||
vectorError()
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format',
|
||||
`${base_url_mapconfig}/static/named/:template_id/:width/:height.:format`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
userMiddleware(),
|
||||
allowQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
|
||||
this.prepareContext,
|
||||
this.getNamedMapProvider('STATIC_VIZ_MAP'),
|
||||
this.getAffectedTables(),
|
||||
this.getTemplate('STATIC_VIZ_MAP'),
|
||||
this.prepareLayerFilterFromPreviewLayers('STATIC_VIZ_MAP'),
|
||||
this.getStaticImageOptions(),
|
||||
this.getImage('STATIC_VIZ_MAP'),
|
||||
this.incrementMapViews(),
|
||||
this.setSurrogateKey(),
|
||||
this.setCacheChannelHeader(),
|
||||
this.setLastModifiedHeader(),
|
||||
this.setCacheControlHeader(),
|
||||
this.setContentTypeHeader(),
|
||||
this.respond()
|
||||
getNamedMapProvider({
|
||||
namedMapProviderCache: this.namedMapProviderCache,
|
||||
label: 'STATIC_VIZ_MAP', forcedFormat: 'png'
|
||||
}),
|
||||
getAffectedTables(),
|
||||
getTemplate({ label: 'STATIC_VIZ_MAP' }),
|
||||
prepareLayerFilterFromPreviewLayers({
|
||||
namedMapProviderCache: this.namedMapProviderCache,
|
||||
label: 'STATIC_VIZ_MAP'
|
||||
}),
|
||||
getStaticImageOptions({ tablesExtentApi: this.tablesExtentApi }),
|
||||
getImage({ previewBackend: this.previewBackend, label: 'STATIC_VIZ_MAP' }),
|
||||
incrementMapViews({ metadataBackend: this.metadataBackend }),
|
||||
setSurrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
setCacheChannelHeader(),
|
||||
setLastModifiedHeader(),
|
||||
setCacheControlHeader(),
|
||||
setContentTypeHeader(),
|
||||
sendResponse()
|
||||
);
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.getNamedMapProvider = function (label) {
|
||||
function getNamedMapProvider ({ namedMapProviderCache, label, forcedFormat = null }) {
|
||||
return function getNamedMapProviderMiddleware (req, res, next) {
|
||||
const { user } = res.locals;
|
||||
const { config, auth_token } = req.query;
|
||||
const { template_id } = req.params;
|
||||
|
||||
// We force always the tile to be generated using PNG because
|
||||
// is the only format we support by now
|
||||
res.locals.format = 'png';
|
||||
res.locals.layer = res.locals.layer || 'all';
|
||||
if (forcedFormat) {
|
||||
res.locals.format = forcedFormat;
|
||||
res.locals.layer = res.locals.layer || 'all';
|
||||
}
|
||||
|
||||
const params = getRequestParams(res.locals);
|
||||
|
||||
this.namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, namedMapProvider) => {
|
||||
namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, namedMapProvider) => {
|
||||
if (err) {
|
||||
err.label = label;
|
||||
return next(err);
|
||||
@@ -103,10 +117,10 @@ NamedMapsController.prototype.getNamedMapProvider = function (label) {
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.getAffectedTables = function () {
|
||||
function getAffectedTables () {
|
||||
return function getAffectedTables (req, res, next) {
|
||||
const { namedMapProvider } = res.locals;
|
||||
|
||||
@@ -121,10 +135,10 @@ NamedMapsController.prototype.getAffectedTables = function () {
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.getTemplate = function (label) {
|
||||
function getTemplate ({ label }) {
|
||||
return function getTemplateMiddleware (req, res, next) {
|
||||
const { namedMapProvider } = res.locals;
|
||||
|
||||
@@ -139,9 +153,9 @@ NamedMapsController.prototype.getTemplate = function (label) {
|
||||
next();
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (label) {
|
||||
function prepareLayerFilterFromPreviewLayers ({ namedMapProviderCache, label }) {
|
||||
return function prepareLayerFilterFromPreviewLayersMiddleware (req, res, next) {
|
||||
const { user, template } = res.locals;
|
||||
const { template_id } = req.params;
|
||||
@@ -170,7 +184,7 @@ NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (la
|
||||
params.layer = layerVisibilityFilter.join(',');
|
||||
|
||||
// recreates the provider
|
||||
this.namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, provider) => {
|
||||
namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, provider) => {
|
||||
if (err) {
|
||||
err.label = label;
|
||||
return next(err);
|
||||
@@ -180,14 +194,14 @@ NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (la
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.getTile = function (label) {
|
||||
function getTile ({ tileBackend, label }) {
|
||||
return function getTileMiddleware (req, res, next) {
|
||||
const { namedMapProvider } = res.locals;
|
||||
|
||||
this.tileBackend.getTile(namedMapProvider, req.params, (err, tile, headers, stats) => {
|
||||
tileBackend.getTile(namedMapProvider, req.params, (err, tile, headers, stats) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
@@ -201,10 +215,10 @@ NamedMapsController.prototype.getTile = function (label) {
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.getStaticImageOptions = function () {
|
||||
function getStaticImageOptions ({ tablesExtentApi }) {
|
||||
return function getStaticImageOptionsMiddleware(req, res, next) {
|
||||
const { user, namedMapProvider, template } = res.locals;
|
||||
|
||||
@@ -228,7 +242,7 @@ NamedMapsController.prototype.getStaticImageOptions = function () {
|
||||
return next();
|
||||
}
|
||||
|
||||
this.tablesExtentApi.getBounds(user, affectedTables, (err, bounds) => {
|
||||
tablesExtentApi.getBounds(user, affectedTables, (err, bounds) => {
|
||||
if (err) {
|
||||
return next();
|
||||
}
|
||||
@@ -238,8 +252,8 @@ NamedMapsController.prototype.getStaticImageOptions = function () {
|
||||
return next();
|
||||
});
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function getImageOptions (params, template) {
|
||||
const { zoom, lon, lat, bbox } = params;
|
||||
@@ -306,7 +320,7 @@ function getImageOptionsFromBoundingBox (bbox = '') {
|
||||
}
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.getImage = function (label) {
|
||||
function getImage({ previewBackend, label }) {
|
||||
return function getImageMiddleware (req, res, next) {
|
||||
const { imageOpts, namedMapProvider } = res.locals;
|
||||
const { zoom, center, bounds } = imageOpts;
|
||||
@@ -319,7 +333,7 @@ NamedMapsController.prototype.getImage = function (label) {
|
||||
const format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
||||
|
||||
if (zoom !== undefined && center) {
|
||||
return this.previewBackend.getImage(namedMapProvider, format, width, height, zoom, center,
|
||||
return previewBackend.getImage(namedMapProvider, format, width, height, zoom, center,
|
||||
(err, image, headers, stats) => {
|
||||
if (err) {
|
||||
err.label = label;
|
||||
@@ -334,7 +348,7 @@ NamedMapsController.prototype.getImage = function (label) {
|
||||
});
|
||||
}
|
||||
|
||||
this.previewBackend.getImage(namedMapProvider, format, width, height, bounds, (err, image, headers, stats) => {
|
||||
previewBackend.getImage(namedMapProvider, format, width, height, bounds, (err, image, headers, stats) => {
|
||||
if (err) {
|
||||
err.label = label;
|
||||
return next(err);
|
||||
@@ -346,14 +360,14 @@ NamedMapsController.prototype.getImage = function (label) {
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function incrementMapViewsError (ctx) {
|
||||
return `ERROR: failed to increment mapview count for user '${ctx.user}': ${ctx.err}`;
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.incrementMapViews = function () {
|
||||
function incrementMapViews ({ metadataBackend }) {
|
||||
return function incrementMapViewsMiddleware(req, res, next) {
|
||||
const { user, namedMapProvider } = res.locals;
|
||||
|
||||
@@ -365,7 +379,7 @@ NamedMapsController.prototype.incrementMapViews = function () {
|
||||
|
||||
const statTag = mapConfig.obj().stat_tag;
|
||||
|
||||
this.metadataBackend.incMapviewCount(user, statTag, (err) => {
|
||||
metadataBackend.incMapviewCount(user, statTag, (err) => {
|
||||
if (err) {
|
||||
global.logger.log(incrementMapViewsError({ user, err }));
|
||||
}
|
||||
@@ -373,8 +387,8 @@ NamedMapsController.prototype.incrementMapViews = function () {
|
||||
next();
|
||||
});
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function templateZoomCenter(view) {
|
||||
if (view.zoom !== undefined && view.center) {
|
||||
@@ -406,7 +420,22 @@ function templateBounds(view) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.setCacheChannelHeader = function () {
|
||||
function setSurrogateKeyHeader ({ surrogateKeysCache }) {
|
||||
return function setSurrogateKeyHeaderMiddleware(req, res, next) {
|
||||
const { user, namedMapProvider, affectedTablesAndLastUpdate } = res.locals;
|
||||
|
||||
surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, namedMapProvider.getTemplateName()));
|
||||
if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
|
||||
if (affectedTablesAndLastUpdate.tables.length > 0) {
|
||||
surrogateKeysCache.tag(res, affectedTablesAndLastUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function setCacheChannelHeader () {
|
||||
return function setCacheChannelHeaderMiddleware (req, res, next) {
|
||||
const { affectedTablesAndLastUpdate } = res.locals;
|
||||
|
||||
@@ -416,24 +445,9 @@ NamedMapsController.prototype.setCacheChannelHeader = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.setSurrogateKey = function () {
|
||||
return function setSurrogateKeyMiddleware(req, res, next) {
|
||||
const { user, namedMapProvider, affectedTablesAndLastUpdate } = res.locals;
|
||||
|
||||
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, namedMapProvider.getTemplateName()));
|
||||
if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
|
||||
if (affectedTablesAndLastUpdate.tables.length > 0) {
|
||||
this.surrogateKeysCache.tag(res, affectedTablesAndLastUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.setLastModifiedHeader = function () {
|
||||
function setLastModifiedHeader () {
|
||||
return function setLastModifiedHeaderMiddleware(req, res, next) {
|
||||
const { affectedTablesAndLastUpdate } = res.locals;
|
||||
|
||||
@@ -450,9 +464,9 @@ NamedMapsController.prototype.setLastModifiedHeader = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.setCacheControlHeader = function () {
|
||||
function setCacheControlHeader () {
|
||||
return function setCacheControlHeaderMiddleware(req, res, next) {
|
||||
const { affectedTablesAndLastUpdate } = res.locals;
|
||||
|
||||
@@ -465,9 +479,9 @@ NamedMapsController.prototype.setCacheControlHeader = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.setContentTypeHeader = function () {
|
||||
function setContentTypeHeader () {
|
||||
return function setContentTypeHeaderMiddleware(req, res, next) {
|
||||
const { headers = {} } = res.locals;
|
||||
|
||||
@@ -475,10 +489,10 @@ NamedMapsController.prototype.setContentTypeHeader = function () {
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsController.prototype.respond = function () {
|
||||
return function respondMiddleware (req, res) {
|
||||
function sendResponse () {
|
||||
return function sendResponseMiddleware (req, res) {
|
||||
const { body, stats = {}, format } = res.locals;
|
||||
|
||||
req.profiler.done('render-' + format);
|
||||
@@ -487,4 +501,4 @@ NamedMapsController.prototype.respond = function () {
|
||||
res.status(200);
|
||||
res.send(body);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const { templateName } = require('../backends/template_maps');
|
||||
const cors = require('../middleware/cors');
|
||||
const userMiddleware = require('../middleware/user');
|
||||
const localsMiddleware = require('../middleware/context/locals');
|
||||
const credentialsMiddleware = require('../middleware/context/credentials');
|
||||
|
||||
/**
|
||||
* @param {AuthApi} authApi
|
||||
@@ -21,43 +23,58 @@ NamedMapsAdminController.prototype.register = function (app) {
|
||||
app.post(
|
||||
`${base_url_templated}/`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.checkContentType('POST', 'POST TEMPLATE'),
|
||||
this.authorizedByAPIKey('create', 'POST TEMPLATE'),
|
||||
this.create()
|
||||
userMiddleware(),
|
||||
localsMiddleware(),
|
||||
credentialsMiddleware(),
|
||||
checkContentType({ action: 'POST', label: 'POST TEMPLATE' }),
|
||||
authorizedByAPIKey({ authApi: this.authApi, action: 'create', label: 'POST TEMPLATE' }),
|
||||
createTemplate({ templateMaps: this.templateMaps }),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.put(
|
||||
`${base_url_templated}/:template_id`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.checkContentType('PUT', 'PUT TEMPLATE'),
|
||||
this.authorizedByAPIKey('update', 'PUT TEMPLATE'),
|
||||
this.update()
|
||||
userMiddleware(),
|
||||
localsMiddleware(),
|
||||
credentialsMiddleware(),
|
||||
checkContentType({ action: 'PUT', label: 'PUT TEMPLATE' }),
|
||||
authorizedByAPIKey({ authApi: this.authApi, action: 'update', label: 'PUT TEMPLATE' }),
|
||||
updateTemplate({ templateMaps: this.templateMaps }),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
`${base_url_templated}/:template_id`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.authorizedByAPIKey('get', 'GET TEMPLATE'),
|
||||
this.retrieve()
|
||||
userMiddleware(),
|
||||
localsMiddleware(),
|
||||
credentialsMiddleware(),
|
||||
authorizedByAPIKey({ authApi: this.authApi, action: 'get', label: 'GET TEMPLATE' }),
|
||||
retrieveTemplate({ templateMaps: this.templateMaps }),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.delete(
|
||||
`${base_url_templated}/:template_id`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.authorizedByAPIKey('delete', 'DELETE TEMPLATE'),
|
||||
this.destroy()
|
||||
userMiddleware(),
|
||||
localsMiddleware(),
|
||||
credentialsMiddleware(),
|
||||
authorizedByAPIKey({ authApi: this.authApi, action: 'delete', label: 'DELETE TEMPLATE' }),
|
||||
destroyTemplate({ templateMaps: this.templateMaps }),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
`${base_url_templated}/`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.authorizedByAPIKey('list', 'GET TEMPLATE LIST'),
|
||||
this.list()
|
||||
userMiddleware(),
|
||||
localsMiddleware(),
|
||||
credentialsMiddleware(),
|
||||
authorizedByAPIKey({ authApi: this.authApi, action: 'list', label: 'GET TEMPLATE LIST' }),
|
||||
listTemplates({ templateMaps: this.templateMaps }),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.options(
|
||||
@@ -66,11 +83,23 @@ NamedMapsAdminController.prototype.register = function (app) {
|
||||
);
|
||||
};
|
||||
|
||||
NamedMapsAdminController.prototype.authorizedByAPIKey = function (action, label) {
|
||||
function checkContentType ({ action, label }) {
|
||||
return function checkContentTypeMiddleware (req, res, next) {
|
||||
if (!req.is('application/json')) {
|
||||
const error = new Error(`template ${action} data must be of type application/json`);
|
||||
error.label = label;
|
||||
return next(error);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
function authorizedByAPIKey ({ authApi, action, label }) {
|
||||
return function authorizedByAPIKeyMiddleware (req, res, next) {
|
||||
const { user } = res.locals;
|
||||
|
||||
this.authApi.authorizedByAPIKey(user, req, (err, authenticated) => {
|
||||
authApi.authorizedByAPIKey(user, res, (err, authenticated) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -84,65 +113,52 @@ NamedMapsAdminController.prototype.authorizedByAPIKey = function (action, label)
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
NamedMapsAdminController.prototype.checkContentType = function (action, label) {
|
||||
return function checkContentTypeMiddleware (req, res, next) {
|
||||
if (!req.is('application/json')) {
|
||||
const error = new Error(`template ${action} data must be of type application/json`);
|
||||
error.label = label;
|
||||
return next(error);
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsAdminController.prototype.create = function () {
|
||||
function createTemplate ({ templateMaps }) {
|
||||
return function createTemplateMiddleware (req, res, next) {
|
||||
const { user } = res.locals;
|
||||
const template = req.body;
|
||||
|
||||
this.templateMaps.addTemplate(user, template, (err, templateId) => {
|
||||
templateMaps.addTemplate(user, template, (err, templateId) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.status(200);
|
||||
res.body = { template_id: templateId };
|
||||
|
||||
const method = req.query.callback ? 'jsonp' : 'json';
|
||||
res[method]({ template_id: templateId });
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsAdminController.prototype.update = function () {
|
||||
function updateTemplate ({ templateMaps }) {
|
||||
return function updateTemplateMiddleware (req, res, next) {
|
||||
const { user } = res.locals;
|
||||
const template = req.body;
|
||||
const templateId = templateName(req.params.template_id);
|
||||
|
||||
this.templateMaps.updTemplate(user, templateId, template, (err) => {
|
||||
templateMaps.updTemplate(user, templateId, template, (err) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.status(200);
|
||||
res.body = { template_id: templateId };
|
||||
|
||||
const method = req.query.callback ? 'jsonp' : 'json';
|
||||
res[method]({ template_id: templateId });
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsAdminController.prototype.retrieve = function () {
|
||||
function retrieveTemplate ({ templateMaps }) {
|
||||
return function retrieveTemplateMiddleware (req, res, next) {
|
||||
req.profiler.start('windshaft-cartodb.get_template');
|
||||
|
||||
const { user } = res.locals;
|
||||
const templateId = templateName(req.params.template_id);
|
||||
|
||||
this.templateMaps.getTemplate(user, templateId, (err, template) => {
|
||||
templateMaps.getTemplate(user, templateId, (err, template) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -156,49 +172,56 @@ NamedMapsAdminController.prototype.retrieve = function () {
|
||||
// so we remove it before returning to the user
|
||||
delete template.auth_id;
|
||||
|
||||
res.status(200);
|
||||
res.body = { template };
|
||||
|
||||
const method = req.query.callback ? 'jsonp' : 'json';
|
||||
res[method]({ template });
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsAdminController.prototype.destroy = function () {
|
||||
function destroyTemplate ({ templateMaps }) {
|
||||
return function destroyTemplateMiddleware (req, res, next) {
|
||||
req.profiler.start('windshaft-cartodb.delete_template');
|
||||
|
||||
const { user } = res.locals;
|
||||
const templateId = templateName(req.params.template_id);
|
||||
|
||||
this.templateMaps.delTemplate(user, templateId, (err/* , tpl_val */) => {
|
||||
templateMaps.delTemplate(user, templateId, (err/* , tpl_val */) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.status(204);
|
||||
res.statusCode = 204;
|
||||
res.body = '';
|
||||
|
||||
const method = req.query.callback ? 'jsonp' : 'json';
|
||||
res[method]('');
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
NamedMapsAdminController.prototype.list = function () {
|
||||
function listTemplates ({ templateMaps }) {
|
||||
return function listTemplatesMiddleware (req, res, next) {
|
||||
req.profiler.start('windshaft-cartodb.get_template_list');
|
||||
|
||||
const { user } = res.locals;
|
||||
|
||||
this.templateMaps.listTemplates(user, (err, templateIds) => {
|
||||
templateMaps.listTemplates(user, (err, templateIds) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.status(200);
|
||||
res.body = { template_ids: templateIds };
|
||||
|
||||
const method = req.query.callback ? 'jsonp' : 'json';
|
||||
res[method]({ template_ids: templateIds });
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function sendResponse () {
|
||||
return function sendResponseMiddleware (req, res) {
|
||||
res.status(res.statusCode || 200);
|
||||
|
||||
const method = req.query.callback ? 'jsonp' : 'json';
|
||||
res[method](res.body);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
module.exports = function allowQueryParams(params) {
|
||||
module.exports = function allowQueryParams (params) {
|
||||
if (!Array.isArray(params)) {
|
||||
throw new Error('allowQueryParams must receive an Array of params');
|
||||
}
|
||||
return function allowQueryParamsMiddleware(req, res, next) {
|
||||
|
||||
return function allowQueryParamsMiddleware (req, res, next) {
|
||||
res.locals.allowedQueryParams = params;
|
||||
next();
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
module.exports = function authorizeMiddleware (authApi) {
|
||||
return function (req, res, next) {
|
||||
req.profiler.done('req2params.setup');
|
||||
|
||||
module.exports = function authorize (authApi) {
|
||||
return function authorizeMiddleware (req, res, next) {
|
||||
authApi.authorize(req, res, (err, authorized) => {
|
||||
req.profiler.done('authorize');
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
85
lib/cartodb/middleware/context/credentials.js
Normal file
85
lib/cartodb/middleware/context/credentials.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const basicAuth = require('basic-auth');
|
||||
|
||||
module.exports = function credentials () {
|
||||
return function credentialsMiddleware(req, res, next) {
|
||||
const apikeyCredentials = getApikeyCredentialsFromRequest(req);
|
||||
|
||||
res.locals.api_key = apikeyCredentials.token;
|
||||
res.locals.basicAuthUsername = apikeyCredentials.username;
|
||||
res.set('vary', 'Authorization'); //Honor Authorization header when caching.
|
||||
|
||||
return next();
|
||||
};
|
||||
};
|
||||
|
||||
function getApikeyCredentialsFromRequest(req) {
|
||||
let apikeyCredentials = {
|
||||
token: null,
|
||||
username: null,
|
||||
};
|
||||
|
||||
for (let getter of apikeyGetters) {
|
||||
apikeyCredentials = getter(req);
|
||||
if (apikeyTokenFound(apikeyCredentials)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return apikeyCredentials;
|
||||
}
|
||||
|
||||
const apikeyGetters = [
|
||||
getApikeyTokenFromHeaderAuthorization,
|
||||
getApikeyTokenFromRequestQueryString,
|
||||
getApikeyTokenFromRequestBody,
|
||||
];
|
||||
|
||||
function getApikeyTokenFromHeaderAuthorization(req) {
|
||||
const credentials = basicAuth(req);
|
||||
|
||||
if (credentials) {
|
||||
return {
|
||||
username: credentials.username,
|
||||
token: credentials.pass
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
username: null,
|
||||
token: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getApikeyTokenFromRequestQueryString(req) {
|
||||
let token = null;
|
||||
|
||||
if (req.query && req.query.api_key) {
|
||||
token = req.query.api_key;
|
||||
} else if (req.query && req.query.map_key) {
|
||||
token = req.query.map_key;
|
||||
}
|
||||
|
||||
return {
|
||||
username: null,
|
||||
token: token,
|
||||
};
|
||||
}
|
||||
|
||||
function getApikeyTokenFromRequestBody(req) {
|
||||
let token = null;
|
||||
|
||||
if (req.body && req.body.api_key) {
|
||||
token = req.body.api_key;
|
||||
} else if (req.body && req.body.map_key) {
|
||||
token = req.body.map_key;
|
||||
}
|
||||
|
||||
return {
|
||||
username: null,
|
||||
token: token,
|
||||
};
|
||||
}
|
||||
|
||||
function apikeyTokenFound(apikey) {
|
||||
return !!apikey && !!apikey.token;
|
||||
}
|
||||
@@ -1,31 +1,30 @@
|
||||
const _ = require('underscore');
|
||||
|
||||
module.exports = function dbConnSetupMiddleware(pgConnection) {
|
||||
return function dbConnSetup(req, res, next) {
|
||||
const user = res.locals.user;
|
||||
module.exports = function dbConnSetup (pgConnection) {
|
||||
return function dbConnSetupMiddleware (req, res, next) {
|
||||
const { user } = res.locals;
|
||||
|
||||
pgConnection.setDBConn(user, res.locals, (err) => {
|
||||
req.profiler.done('dbConnSetup');
|
||||
|
||||
if (err) {
|
||||
if (err.message && -1 !== err.message.indexOf('name not found')) {
|
||||
err.http_status = 404;
|
||||
}
|
||||
req.profiler.done('req2params');
|
||||
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Add default database connection parameters
|
||||
// if none given
|
||||
_.defaults(res.locals, {
|
||||
dbuser: global.environment.postgres.user,
|
||||
dbpassword: global.environment.postgres.password,
|
||||
dbhost: global.environment.postgres.host,
|
||||
dbport: global.environment.postgres.port
|
||||
});
|
||||
|
||||
|
||||
res.set('X-Served-By-DB-Host', res.locals.dbhost);
|
||||
|
||||
req.profiler.done('req2params');
|
||||
|
||||
next(null);
|
||||
next();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
const locals = require('./locals');
|
||||
const cleanUpQueryParams = require('./clean-up-query-params');
|
||||
const layergroupToken = require('./layergroup-token');
|
||||
const credentials = require('./credentials');
|
||||
const authorize = require('./authorize');
|
||||
const dbConnSetup = require('./db-conn-setup');
|
||||
|
||||
module.exports = function prepareContextMiddleware(authApi, pgConnection) {
|
||||
return [
|
||||
locals,
|
||||
locals(),
|
||||
cleanUpQueryParams(),
|
||||
layergroupToken,
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(authApi),
|
||||
dbConnSetup(pgConnection)
|
||||
];
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
var LayergroupToken = require('../../models/layergroup-token');
|
||||
|
||||
module.exports = function layergroupTokenMiddleware(req, res, next) {
|
||||
if (!res.locals.token) {
|
||||
return next();
|
||||
}
|
||||
|
||||
var user = res.locals.user;
|
||||
|
||||
var layergroupToken = LayergroupToken.parse(res.locals.token);
|
||||
res.locals.token = layergroupToken.token;
|
||||
res.locals.cache_buster = layergroupToken.cacheBuster;
|
||||
|
||||
if (layergroupToken.signer) {
|
||||
res.locals.signer = layergroupToken.signer;
|
||||
if (!res.locals.signer) {
|
||||
res.locals.signer = user;
|
||||
} else if (res.locals.signer !== user) {
|
||||
var err = new Error(`Cannot use map signature of user "${res.locals.signer}" on db of user "${user}"`);
|
||||
err.type = 'auth';
|
||||
err.http_status = 403;
|
||||
if (req.query && req.query.callback) {
|
||||
err.http_status = 200;
|
||||
}
|
||||
|
||||
req.profiler.done('req2params');
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
const LayergroupToken = require('../../models/layergroup-token');
|
||||
const authErrorMessageTemplate = function (signer, user) {
|
||||
return `Cannot use map signature of user "${signer}" on db of user "${user}"`;
|
||||
};
|
||||
|
||||
module.exports = function layergroupToken () {
|
||||
return function layergroupTokenMiddleware (req, res, next) {
|
||||
if (!res.locals.token) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const user = res.locals.user;
|
||||
|
||||
const layergroupToken = LayergroupToken.parse(res.locals.token);
|
||||
|
||||
res.locals.token = layergroupToken.token;
|
||||
res.locals.cache_buster = layergroupToken.cacheBuster;
|
||||
|
||||
if (layergroupToken.signer) {
|
||||
res.locals.signer = layergroupToken.signer;
|
||||
|
||||
if (res.locals.signer !== user) {
|
||||
const err = new Error(authErrorMessageTemplate(res.locals.signer, user));
|
||||
err.type = 'auth';
|
||||
err.http_status = (req.query && req.query.callback) ? 200: 403;
|
||||
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module.exports = function localsMiddleware(req, res, next) {
|
||||
// save req.params in res.locals
|
||||
res.locals = Object.assign(req.params || {}, res.locals);
|
||||
module.exports = function locals () {
|
||||
return function localsMiddleware (req, res, next) {
|
||||
res.locals = Object.assign(req.params || {}, res.locals);
|
||||
|
||||
next();
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
module.exports = function cors(extraHeaders) {
|
||||
return function(req, res, next) {
|
||||
var baseHeaders = "X-Requested-With, X-Prototype-Version, X-CSRF-Token";
|
||||
module.exports = function cors (extraHeaders) {
|
||||
return function corsMiddleware (req, res, next) {
|
||||
let baseHeaders = "X-Requested-With, X-Prototype-Version, X-CSRF-Token";
|
||||
|
||||
if(extraHeaders) {
|
||||
baseHeaders += ", " + extraHeaders;
|
||||
}
|
||||
|
||||
res.set("Access-Control-Allow-Origin", "*");
|
||||
res.set("Access-Control-Allow-Headers", baseHeaders);
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const LZMA = require('lzma').LZMA;
|
||||
|
||||
const lzmaWorker = new LZMA();
|
||||
module.exports = function lzma () {
|
||||
const lzmaWorker = new LZMA();
|
||||
|
||||
module.exports = function lzmaMiddleware(req, res, next) {
|
||||
if (!req.query.hasOwnProperty('lzma')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Decode (from base64)
|
||||
var lzma = new Buffer(req.query.lzma, 'base64')
|
||||
.toString('binary')
|
||||
.split('')
|
||||
.map(function(c) {
|
||||
return c.charCodeAt(0) - 128;
|
||||
});
|
||||
|
||||
// Decompress
|
||||
lzmaWorker.decompress(lzma, function(result) {
|
||||
try {
|
||||
delete req.query.lzma;
|
||||
Object.assign(req.query, JSON.parse(result));
|
||||
next();
|
||||
} catch (err) {
|
||||
next(new Error('Error parsing lzma as JSON: ' + err));
|
||||
return function lzmaMiddleware (req, res, next) {
|
||||
if (!req.query.hasOwnProperty('lzma')) {
|
||||
return next();
|
||||
}
|
||||
});
|
||||
|
||||
// Decode (from base64)
|
||||
var lzma = new Buffer(req.query.lzma, 'base64')
|
||||
.toString('binary')
|
||||
.split('')
|
||||
.map(function(c) {
|
||||
return c.charCodeAt(0) - 128;
|
||||
});
|
||||
|
||||
// Decompress
|
||||
lzmaWorker.decompress(lzma, function(result) {
|
||||
try {
|
||||
delete req.query.lzma;
|
||||
Object.assign(req.query, JSON.parse(result));
|
||||
|
||||
req.profiler.done('lzma');
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
next(new Error('Error parsing lzma as JSON: ' + err));
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,10 +2,10 @@ const Profiler = require('../stats/profiler_proxy');
|
||||
const debug = require('debug')('windshaft:cartodb:stats');
|
||||
const onHeaders = require('on-headers');
|
||||
|
||||
module.exports = function statsMiddleware(options) {
|
||||
module.exports = function stats (options) {
|
||||
const { enabled = true, statsClient } = options;
|
||||
|
||||
return function stats(req, res, next) {
|
||||
return function statsMiddleware (req, res, next) {
|
||||
req.profiler = new Profiler({
|
||||
statsd_client: statsClient,
|
||||
profile: enabled
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
var CdbRequest = require('../models/cdb_request');
|
||||
var cdbRequest = new CdbRequest();
|
||||
const CdbRequest = require('../models/cdb_request');
|
||||
|
||||
module.exports = function userMiddleware(req, res, next) {
|
||||
res.locals.user = cdbRequest.userByReq(req);
|
||||
module.exports = function user () {
|
||||
const cdbRequest = new CdbRequest();
|
||||
|
||||
next();
|
||||
return function userMiddleware(req, res, next) {
|
||||
res.locals.user = cdbRequest.userByReq(req);
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const timeoutErrorVectorTile = fs.readFileSync(__dirname + '/../../../assets/render-timeout-fallback.mvt');
|
||||
|
||||
module.exports = function vectorError() {
|
||||
|
||||
@@ -131,7 +131,12 @@ const dimensionDefs = ctx => {
|
||||
// This is equivalent to `${256/ctx.res}*CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!))`
|
||||
// This is defined by the ctx.res parameter, which is the number of grid cells per tile linear dimension
|
||||
// (i.e. each tile is divided into ctx.res*ctx.res cells).
|
||||
const gridResolution = ctx => `(${256*0.00028/ctx.res}*!scale_denominator!)::double precision`;
|
||||
// We limit the the minimum resolution to avoid division by zero problems. The limit used is
|
||||
// the pixel size of zoom level 30 (i.e. 1/2*(30+8) of the full earth web-mercator extent), which is about 0.15 mm.
|
||||
const gridResolution = ctx => {
|
||||
const minimumResolution = 2*Math.PI*6378137/Math.pow(2,38);
|
||||
return `GREATEST(${256*0.00028/ctx.res}*!scale_denominator!, ${minimumResolution})::double precision`;
|
||||
};
|
||||
|
||||
// Notes:
|
||||
// * We need to filter spatially using !bbox! to make the queries efficient because
|
||||
|
||||
@@ -2,19 +2,17 @@ const BaseDataview = require('./base');
|
||||
const debug = require('debug')('windshaft:dataview:aggregation');
|
||||
|
||||
const filteredQueryTpl = ctx => `
|
||||
filtered_source AS (
|
||||
SELECT *
|
||||
FROM (${ctx.query}) _cdb_filtered_source
|
||||
${ctx.aggregationColumn && ctx.isFloatColumn ? `
|
||||
WHERE
|
||||
${ctx.aggregationColumn} != 'infinity'::float
|
||||
AND
|
||||
${ctx.aggregationColumn} != '-infinity'::float
|
||||
AND
|
||||
${ctx.aggregationColumn} != 'NaN'::float` :
|
||||
''
|
||||
}
|
||||
)
|
||||
SELECT *
|
||||
FROM (${ctx.query}) _cdb_filtered_source
|
||||
${ctx.aggregationColumn && ctx.isFloatColumn ? `
|
||||
WHERE
|
||||
${ctx.aggregationColumn} != 'infinity'::float
|
||||
AND
|
||||
${ctx.aggregationColumn} != '-infinity'::float
|
||||
AND
|
||||
${ctx.aggregationColumn} != 'NaN'::float` :
|
||||
''
|
||||
}
|
||||
`;
|
||||
|
||||
const summaryQueryTpl = ctx => `
|
||||
@@ -43,8 +41,8 @@ const rankedCategoriesQueryTpl = ctx => `
|
||||
${ctx.column} AS category,
|
||||
${ctx.aggregationFn} AS value,
|
||||
row_number() OVER (ORDER BY ${ctx.aggregationFn} desc) as rank
|
||||
FROM filtered_source
|
||||
${ctx.aggregationColumn !== null ? `WHERE ${ctx.aggregationColumn} IS NOT NULL` : ''}
|
||||
FROM (${filteredQueryTpl(ctx)}) filtered_source
|
||||
WHERE ${ctx.aggregation === "count" ? `${ctx.column}` : `${ctx.aggregationColumn}`} IS NOT NULL
|
||||
GROUP BY ${ctx.column}
|
||||
ORDER BY 2 DESC
|
||||
)
|
||||
@@ -134,7 +132,6 @@ const aggregationFnQueryTpl = ctx => `${ctx.aggregation}(${ctx.aggregationColumn
|
||||
|
||||
const aggregationDataviewQueryTpl = ctx => `
|
||||
WITH
|
||||
${filteredQueryTpl(ctx)},
|
||||
${summaryQueryTpl(ctx)},
|
||||
${rankedCategoriesQueryTpl(ctx)},
|
||||
${categoriesSummaryMinMaxQueryTpl(ctx)},
|
||||
@@ -282,7 +279,7 @@ module.exports = class Aggregation extends BaseDataview {
|
||||
max_val = 0,
|
||||
categories_count = 0
|
||||
} = result.rows[0] || {};
|
||||
|
||||
|
||||
return {
|
||||
aggregation: this.aggregation,
|
||||
count: count,
|
||||
@@ -293,10 +290,10 @@ module.exports = class Aggregation extends BaseDataview {
|
||||
max: max_val,
|
||||
categoriesCount: categories_count,
|
||||
categories: result.rows.map(({ category, value, agg }) => {
|
||||
return {
|
||||
return {
|
||||
category: agg ? 'Other' : category,
|
||||
value,
|
||||
agg
|
||||
value,
|
||||
agg
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
@@ -108,8 +108,7 @@ MapConfigNamedLayersAdapter.prototype.getMapConfig = function (user, requestMapC
|
||||
var dbAuth = {};
|
||||
|
||||
if (_.some(layers, isNamedTypeLayer)) {
|
||||
// Lazy load dbAuth
|
||||
this.pgConnection.setDBAuth(user, dbAuth, function(err) {
|
||||
this.pgConnection.setDBAuth(user, dbAuth, 'master', function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
@@ -232,19 +232,19 @@ function configHash(config) {
|
||||
module.exports.configHash = configHash;
|
||||
|
||||
NamedMapMapConfigProvider.prototype.setDBParams = function(cdbuser, params, callback) {
|
||||
var self = this;
|
||||
step(
|
||||
function setAuth() {
|
||||
self.pgConnection.setDBAuth(cdbuser, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
assert.ifError(err);
|
||||
self.pgConnection.setDBConn(cdbuser, params, this);
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
this.pgConnection.getDatabaseParams(cdbuser, (err, databaseParams) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
|
||||
params.dbuser = databaseParams.dbuser;
|
||||
params.dbpass = databaseParams.dbpass;
|
||||
params.dbhost = databaseParams.dbhost;
|
||||
params.dbport = databaseParams.dbport;
|
||||
params.dbname = databaseParams.dbname;
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
NamedMapMapConfigProvider.prototype.getTemplateName = function() {
|
||||
|
||||
@@ -377,7 +377,7 @@ function bootstrap(opts) {
|
||||
statsClient: global.statsClient
|
||||
}));
|
||||
|
||||
app.use(lzmaMiddleware);
|
||||
app.use(lzmaMiddleware());
|
||||
|
||||
// temporary measure until we upgrade to newer version expressjs so we can check err.status
|
||||
app.use(function(err, req, res, next) {
|
||||
|
||||
@@ -15,6 +15,7 @@ var rendererConfig = _.defaults(global.environment.renderer || {}, {
|
||||
bufferSize: 64,
|
||||
snapToGrid: false,
|
||||
clipByBox2d: false,
|
||||
metrics: false,
|
||||
limits: {}
|
||||
},
|
||||
http: {}
|
||||
|
||||
168
lib/cartodb/utils/layergroup-metadata.js
Normal file
168
lib/cartodb/utils/layergroup-metadata.js
Normal file
@@ -0,0 +1,168 @@
|
||||
module.exports = class LayergroupMetadata {
|
||||
constructor (resourceLocator) {
|
||||
this.resourceLocator = resourceLocator;
|
||||
}
|
||||
|
||||
// TODO this should take into account several URL patterns
|
||||
addDataviewsAndWidgetsUrls (username, layergroup, mapConfig) {
|
||||
this._addDataviewsUrls(username, layergroup, mapConfig);
|
||||
this._addWidgetsUrl(username, layergroup, mapConfig);
|
||||
}
|
||||
|
||||
_addDataviewsUrls (username, layergroup, mapConfig) {
|
||||
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
|
||||
var dataviews = mapConfig.dataviews || {};
|
||||
|
||||
Object.keys(dataviews).forEach((dataviewName) => {
|
||||
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
|
||||
layergroup.metadata.dataviews[dataviewName] = {
|
||||
url: this.resourceLocator.getUrls(username, resource)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
_addWidgetsUrl (username, layergroup, mapConfig) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map((layer, layerIndex) => {
|
||||
var mapConfigLayer = mapConfig.layers[layerIndex];
|
||||
if (mapConfigLayer.options && mapConfigLayer.options.widgets) {
|
||||
layer.widgets = layer.widgets || {};
|
||||
Object.keys(mapConfigLayer.options.widgets).forEach((widgetName) => {
|
||||
var resource = layergroup.layergroupid + '/' + layerIndex + '/widget/' + widgetName;
|
||||
layer.widgets[widgetName] = {
|
||||
type: mapConfigLayer.options.widgets[widgetName].type,
|
||||
url: this.resourceLocator.getUrls(username, resource)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addAnalysesMetadata (username, layergroup, analysesResults, includeQuery) {
|
||||
includeQuery = includeQuery || false;
|
||||
analysesResults = analysesResults || [];
|
||||
layergroup.metadata.analyses = [];
|
||||
|
||||
analysesResults.forEach((analysis) => {
|
||||
var nodes = analysis.getNodes();
|
||||
layergroup.metadata.analyses.push({
|
||||
nodes: nodes.reduce((nodesIdMap, node) => {
|
||||
if (node.params.id) {
|
||||
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
|
||||
var nodeRepr = {
|
||||
status: node.getStatus(),
|
||||
url: this.resourceLocator.getUrls(username, nodeResource)
|
||||
};
|
||||
if (includeQuery) {
|
||||
nodeRepr.query = node.getQuery();
|
||||
}
|
||||
if (node.getStatus() === 'failed') {
|
||||
nodeRepr.error_message = node.getErrorMessage();
|
||||
}
|
||||
nodesIdMap[node.params.id] = nodeRepr;
|
||||
}
|
||||
|
||||
return nodesIdMap;
|
||||
}, {})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addAggregationContextMetadata (layergroup, mapConfig, context) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
|
||||
if (context.aggregation && Array.isArray(context.aggregation.layers)) {
|
||||
layer.meta.aggregation = context.aggregation.layers[layerIndex];
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addTileJsonMetadata (layergroup, user, mapconfig) {
|
||||
const isVectorOnlyMapConfig = mapconfig.isVectorOnlyMapConfig();
|
||||
let hasMapnikLayers = false;
|
||||
|
||||
layergroup.metadata.layers.forEach((layerMetadata, index) => {
|
||||
const layerId = mapconfig.getLayerId(index);
|
||||
const rasterResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.png`;
|
||||
if (mapconfig.layerType(index) === 'mapnik') {
|
||||
hasMapnikLayers = true;
|
||||
const vectorResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.mvt`;
|
||||
const layerTilejson = {
|
||||
vector: this._getTilejson(this.resourceLocator.getTileUrls(user, vectorResource))
|
||||
};
|
||||
if (!isVectorOnlyMapConfig) {
|
||||
let grids = null;
|
||||
const layer = mapconfig.getLayer(index);
|
||||
if (layer.options.interactivity) {
|
||||
const gridResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.grid.json`;
|
||||
grids = this.resourceLocator.getTileUrls(user, gridResource);
|
||||
}
|
||||
layerTilejson.raster = this._getTilejson(
|
||||
this.resourceLocator.getTileUrls(user, rasterResource),
|
||||
grids
|
||||
);
|
||||
}
|
||||
layerMetadata.tilejson = layerTilejson;
|
||||
} else {
|
||||
layerMetadata.tilejson = {
|
||||
raster: this._getTilejson(this.resourceLocator.getTileUrls(user, rasterResource))
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const tilejson = {};
|
||||
const url = {};
|
||||
|
||||
if (hasMapnikLayers) {
|
||||
const vectorResource = `${layergroup.layergroupid}/{z}/{x}/{y}.mvt`;
|
||||
tilejson.vector = this._getTilejson(
|
||||
this.resourceLocator.getTileUrls(user, vectorResource)
|
||||
);
|
||||
url.vector = this._getTemplateUrl(this.resourceLocator.getTemplateUrls(user, vectorResource));
|
||||
|
||||
if (!isVectorOnlyMapConfig) {
|
||||
const rasterResource = `${layergroup.layergroupid}/{z}/{x}/{y}.png`;
|
||||
tilejson.raster = this._getTilejson(
|
||||
this.resourceLocator.getTileUrls(user, rasterResource)
|
||||
);
|
||||
url.raster = this._getTemplateUrl(this.resourceLocator.getTemplateUrls(user, rasterResource));
|
||||
}
|
||||
}
|
||||
|
||||
layergroup.metadata.tilejson = tilejson;
|
||||
layergroup.metadata.url = url;
|
||||
}
|
||||
|
||||
_getTilejson(tiles, grids) {
|
||||
const tilejson = {
|
||||
tilejson: '2.2.0',
|
||||
tiles: tiles.https || tiles.http
|
||||
};
|
||||
|
||||
if (grids) {
|
||||
tilejson.grids = grids.https || grids.http;
|
||||
}
|
||||
|
||||
return tilejson;
|
||||
}
|
||||
|
||||
_getTemplateUrl(url) {
|
||||
return url.https || url.http;
|
||||
}
|
||||
|
||||
addTurboCartoContextMetadata(layergroup, mapConfig, context) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
|
||||
if (context.turboCarto && Array.isArray(context.turboCarto.layers)) {
|
||||
layer.meta.cartocss_meta = context.turboCarto.layers[layerIndex];
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
15
package.json
15
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "5.2.0",
|
||||
"version": "5.4.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -24,11 +24,12 @@
|
||||
"Simon Martin <simon@carto.com>"
|
||||
],
|
||||
"dependencies": {
|
||||
"basic-auth": "^2.0.0",
|
||||
"body-parser": "^1.18.2",
|
||||
"camshaft": "0.60.0",
|
||||
"camshaft": "0.61.4",
|
||||
"cartodb-psql": "0.10.2",
|
||||
"cartodb-query-tables": "0.3.0",
|
||||
"cartodb-redis": "0.14.0",
|
||||
"cartodb-redis": "0.16.0",
|
||||
"debug": "^3.1.0",
|
||||
"dot": "~1.0.2",
|
||||
"express": "~4.16.0",
|
||||
@@ -40,14 +41,14 @@
|
||||
"node-statsd": "~0.0.7",
|
||||
"on-headers": "^1.0.1",
|
||||
"queue-async": "~1.0.7",
|
||||
"redis-mpool": "0.4.1",
|
||||
"request": "^2.83.0",
|
||||
"redis-mpool": "0.5.0",
|
||||
"request": "2.85.0",
|
||||
"semver": "~5.3.0",
|
||||
"step": "~0.0.6",
|
||||
"step-profiler": "~0.3.0",
|
||||
"turbo-carto": "0.20.2",
|
||||
"underscore": "~1.6.0",
|
||||
"windshaft": "4.3.3",
|
||||
"windshaft": "4.6.0",
|
||||
"yargs": "~5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -56,7 +57,7 @@
|
||||
"mocha": "~3.4.1",
|
||||
"moment": "~2.18.1",
|
||||
"nock": "~2.11.0",
|
||||
"redis": "~0.12.1",
|
||||
"redis": "2.8.0",
|
||||
"strftime": "~0.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1438,6 +1438,43 @@ describe('aggregation', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('aggregation should work with attributes', function (done) {
|
||||
this.mapConfig = createVectorMapConfig([
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: POINTS_SQL_1,
|
||||
cartocss: '#layer { marker-width: 7; }',
|
||||
cartocss_version: '2.3.0',
|
||||
aggregation: {
|
||||
threshold: 1
|
||||
},
|
||||
attributes: {
|
||||
id: 'cartodb_id',
|
||||
columns: [
|
||||
'value'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
|
||||
this.testClient.getLayergroup((err, body) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(typeof body.metadata, 'object');
|
||||
assert.ok(Array.isArray(body.metadata.layers));
|
||||
|
||||
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.mvt));
|
||||
body.metadata.layers.forEach(layer => assert.ok(layer.meta.aggregation.png));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,7 +188,7 @@ describe('analysis-layers error cases', function() {
|
||||
]
|
||||
);
|
||||
|
||||
var testClient = new TestClient(mapConfig, 11111);
|
||||
var testClient = new TestClient(mapConfig); //No apikey provided -> using default public apikey
|
||||
|
||||
testClient.getLayergroup({ response: AUTH_ERROR_RESPONSE }, function(err, layergroupResult) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
181
test/acceptance/auth/authorization-fallback.js
Normal file
181
test/acceptance/auth/authorization-fallback.js
Normal file
@@ -0,0 +1,181 @@
|
||||
//Remove this file when Auth fallback is not used anymore
|
||||
// AUTH_FALLBACK
|
||||
|
||||
const assert = require('../../support/assert');
|
||||
const testHelper = require('../../support/test_helper');
|
||||
const CartodbWindshaft = require('../../../lib/cartodb/server');
|
||||
const serverOptions = require('../../../lib/cartodb/server_options');
|
||||
const server = new CartodbWindshaft(serverOptions);
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
|
||||
|
||||
function singleLayergroupConfig(sql, cartocss) {
|
||||
return {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss: cartocss,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
}
|
||||
|
||||
var layergroupUrl = '/api/v1/map';
|
||||
var pointSqlMaster = "select * from test_table_private_1";
|
||||
var pointSqlPublic = "select * from test_table";
|
||||
var keysToDelete;
|
||||
|
||||
describe('authorization fallback', function () {
|
||||
beforeEach(function () {
|
||||
keysToDelete = {};
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
testHelper.deleteRedisKeys(keysToDelete, done);
|
||||
});
|
||||
|
||||
it("succeed with master", function (done) {
|
||||
var layergroup = singleLayergroupConfig(pointSqlMaster, '#layer { marker-fill:red; }');
|
||||
|
||||
assert.response(server,
|
||||
createRequest(layergroup, 'user_previous_to_project_auth', '4444'),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
|
||||
keysToDelete['user:user_previous_to_project_auth:mapviews:global'] = 5;
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it("succeed with default - sending default_public", function (done) {
|
||||
var layergroup = singleLayergroupConfig(pointSqlPublic, '#layer { marker-fill:red; }');
|
||||
|
||||
assert.response(server,
|
||||
createRequest(layergroup, 'user_previous_to_project_auth', 'default_public'),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
|
||||
keysToDelete['user:user_previous_to_project_auth:mapviews:global'] = 5;
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("succeed with default - sending no api key token", function (done) {
|
||||
var layergroup = singleLayergroupConfig(pointSqlPublic, '#layer { marker-fill:red; }');
|
||||
|
||||
assert.response(server,
|
||||
createRequest(layergroup, 'user_previous_to_project_auth'),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
|
||||
keysToDelete['user:user_previous_to_project_auth:mapviews:global'] = 5;
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("succeed with non-existent api key - defaults to default", function (done) {
|
||||
var layergroup = singleLayergroupConfig(pointSqlPublic, '#layer { marker-fill:red; }');
|
||||
|
||||
assert.response(server,
|
||||
createRequest(layergroup, 'user_previous_to_project_auth', 'THIS-API-KEY-DOESNT-EXIST'),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
|
||||
keysToDelete['user:user_previous_to_project_auth:mapviews:global'] = 5;
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("fail with default", function (done) {
|
||||
var layergroup = singleLayergroupConfig(pointSqlMaster, '#layer { marker-fill:red; }');
|
||||
|
||||
assert.response(server,
|
||||
createRequest(layergroup, 'user_previous_to_project_auth', 'default_public'),
|
||||
{
|
||||
status: 403
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ifError(err);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("fail with non-existent api key - defaults to default", function (done) {
|
||||
var layergroup = singleLayergroupConfig(pointSqlMaster, '#layer { marker-fill:red; }');
|
||||
|
||||
assert.response(server,
|
||||
createRequest(layergroup, 'user_previous_to_project_auth', 'THIS-API-KEY-DOESNT-EXIST'),
|
||||
{
|
||||
status: 403
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ifError(err);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
431
test/acceptance/auth/authorization.js
Normal file
431
test/acceptance/auth/authorization.js
Normal file
@@ -0,0 +1,431 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
const assert = require('../../support/assert');
|
||||
const TestClient = require('../../support/test-client');
|
||||
const mapnik = require('windshaft').mapnik;
|
||||
|
||||
const PERMISSION_DENIED_RESPONSE = {
|
||||
status: 403,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
describe('authorization', function() {
|
||||
it('should create a layergroup with regular apikey token', function(done) {
|
||||
const apikeyToken = 'regular1';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * FROM test_table_localhost_regular1',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig, apikeyToken);
|
||||
|
||||
testClient.getLayergroup(function (err, layergroupResult) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(layergroupResult.layergroupid);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create and get a named map tile using a regular apikey token', function (done) {
|
||||
const apikeyToken = 'regular1';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * FROM test_table_localhost_regular1',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig, apikeyToken);
|
||||
|
||||
testClient.getTile(0, 0, 0, function (err, res, tile) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.ok(tile instanceof mapnik.Image);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail getting a named map tile with default apikey token', function (done) {
|
||||
const apikeyTokenCreate = 'regular1';
|
||||
const apikeyTokenGet = 'default_public';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * FROM test_table_localhost_regular1',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const testClientCreate = new TestClient(mapConfig, apikeyTokenCreate);
|
||||
testClientCreate.getLayergroup(function (err, layergroupResult) {
|
||||
assert.ifError(err);
|
||||
const layergroupId = layergroupResult.layergroupid;
|
||||
|
||||
const testClientGet = new TestClient({}, apikeyTokenGet);
|
||||
|
||||
const params = {
|
||||
layergroupid: layergroupId,
|
||||
response: PERMISSION_DENIED_RESPONSE
|
||||
};
|
||||
|
||||
testClientGet.getTile(0, 0, 0, params, function(err, res, body) {
|
||||
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(body.hasOwnProperty('errors'));
|
||||
assert.equal(body.errors.length, 1);
|
||||
assert.ok(body.errors[0].match(/permission denied/), body.errors[0]);
|
||||
|
||||
testClientGet.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail creating a layergroup with default apikey token', function (done) {
|
||||
const apikeyToken = 'default_public';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * FROM test_table_localhost_regular1',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig, apikeyToken);
|
||||
|
||||
testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(layergroupResult.hasOwnProperty('errors'));
|
||||
assert.equal(layergroupResult.errors.length, 1);
|
||||
assert.ok(layergroupResult.errors[0].match(/permission denied/), layergroupResult.errors[0]);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a layergroup with default apikey token', function (done) {
|
||||
const apikeyToken = 'default_public';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * FROM test_table',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig, apikeyToken);
|
||||
|
||||
testClient.getLayergroup(function (err, layergroupResult) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(layergroupResult.layergroupid);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create and get a tile with default apikey token', function (done) {
|
||||
const apikeyToken = 'default_public';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * FROM test_table',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig, apikeyToken);
|
||||
|
||||
testClient.getTile(0, 0, 0, function (err, res, tile) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.ok(tile instanceof mapnik.Image);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if apikey does not grant access to table', function (done) {
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * FROM test_table_localhost_regular1',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig); //no apikey provided, using default
|
||||
|
||||
testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) { //TODO 401
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(layergroupResult.hasOwnProperty('errors'));
|
||||
assert.equal(layergroupResult.errors.length, 1);
|
||||
assert.ok(layergroupResult.errors[0].match(/permission denied/), layergroupResult.errors[0]);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should forbide access to API if API key does not grant access', function (done) {
|
||||
const apikeyToken = 'regular2';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * FROM test_table_localhost_regular1',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig, apikeyToken);
|
||||
|
||||
testClient.getLayergroup({ response: { status: 403 } }, function (err, layergroupResult) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(layergroupResult.hasOwnProperty('errors'));
|
||||
assert.equal(layergroupResult.errors.length, 1);
|
||||
assert.ok(layergroupResult.errors[0].match(/Forbidden/), layergroupResult.errors[0]);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a layergroup with a source analysis using a default apikey token', function (done) {
|
||||
const apikeyToken = 'default_public';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
source: {
|
||||
id: 'HEAD'
|
||||
},
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
],
|
||||
analyses: [
|
||||
{
|
||||
id: 'HEAD',
|
||||
type: 'source',
|
||||
params: {
|
||||
query: 'select * from populated_places_simple_reduced'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig, apikeyToken);
|
||||
|
||||
testClient.getLayergroup(function (err, layergroupResult) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(layergroupResult.layergroupid);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a layergroup with a source analysis using a regular apikey token', function (done) {
|
||||
const apikeyToken = 'regular1';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
source: {
|
||||
id: 'HEAD'
|
||||
},
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
],
|
||||
analyses: [
|
||||
{
|
||||
id: 'HEAD',
|
||||
type: 'source',
|
||||
params: {
|
||||
query: 'select * from test_table_localhost_regular1'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig, apikeyToken);
|
||||
|
||||
testClient.getLayergroup(function (err, layergroupResult) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(layergroupResult.layergroupid);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
// Warning: TBA
|
||||
it('should create a layergroup with a buffer analysis using a regular apikey token', function (done) {
|
||||
const apikeyToken = 'regular1';
|
||||
const mapConfig = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
source: {
|
||||
id: 'HEAD1'
|
||||
},
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
],
|
||||
analyses: [
|
||||
{
|
||||
id: "HEAD1",
|
||||
type: "buffer",
|
||||
params: {
|
||||
source: {
|
||||
id: 'HEAD2',
|
||||
type: 'source',
|
||||
params: {
|
||||
query: 'select * from test_table_localhost_regular1'
|
||||
}
|
||||
},
|
||||
radius: 50000
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const testClient = new TestClient(mapConfig, apikeyToken);
|
||||
|
||||
testClient.getLayergroup(function (err, layergroupResult) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(layergroupResult.layergroupid);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create and get a named map tile using a regular apikey token', function (done) {
|
||||
const apikeyToken = 'regular1';
|
||||
|
||||
const template = {
|
||||
version: '0.0.1',
|
||||
name: 'auth-api-template',
|
||||
placeholders: {
|
||||
buffersize: {
|
||||
type: 'number',
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
version: '1.7.0',
|
||||
layers: [{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: 'select * from test_table_localhost_regular1',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0',
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const testClient = new TestClient(template, apikeyToken);
|
||||
|
||||
testClient.getTile(0, 0, 0, function (err, res, tile) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.ok(tile instanceof mapnik.Image);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail creating a named map using a regular apikey token and a private table', function (done) {
|
||||
const apikeyToken = 'regular1';
|
||||
|
||||
const template = {
|
||||
version: '0.0.1',
|
||||
name: 'auth-api-template-private',
|
||||
placeholders: {
|
||||
buffersize: {
|
||||
type: 'number',
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
version: '1.7.0',
|
||||
layers: [{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: 'select * from populated_places_simple_reduced_private',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0',
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const testClient = new TestClient(template, apikeyToken);
|
||||
|
||||
testClient.getTile(0, 0, 0, { response: PERMISSION_DENIED_RESPONSE }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.ok(body.hasOwnProperty('errors'));
|
||||
assert.equal(body.errors.length, 1);
|
||||
assert.ok(body.errors[0].match(/permission denied/), body.errors[0]);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
1
test/acceptance/cache/cache_headers.js
vendored
1
test/acceptance/cache/cache_headers.js
vendored
@@ -155,6 +155,7 @@ describe('get requests with cache headers', function() {
|
||||
|
||||
assert.ok(res.headers['x-cache-channel']);
|
||||
assert.ok(res.headers['surrogate-key']);
|
||||
assert.equal(res.headers.vary, 'Authorization');
|
||||
if (expectedCacheHeaders) {
|
||||
validateXChannelHeaders(res.headers, expectedCacheHeaders);
|
||||
assert.equal(res.headers['surrogate-key'], expectedCacheHeaders.surrogate_keys);
|
||||
|
||||
@@ -70,12 +70,8 @@ describe('aggregations happy cases', function() {
|
||||
].join(' UNION ALL ');
|
||||
|
||||
operations.forEach(function (operation) {
|
||||
var not = operation === 'count' ? ' not ' : ' ';
|
||||
var description = 'should' +
|
||||
not +
|
||||
'handle NULL values in category and aggregation columns using "' +
|
||||
operation +
|
||||
'" as aggregation operation';
|
||||
var description = 'should handle NULL values in category and aggregation columns using "' +
|
||||
operation + '" as aggregation operation';
|
||||
|
||||
it(description, function (done) {
|
||||
this.testClient = new TestClient(aggregationOperationMapConfig(operation, query, 'cat', 'val'));
|
||||
@@ -96,12 +92,7 @@ describe('aggregations happy cases', function() {
|
||||
}
|
||||
});
|
||||
|
||||
if (operation === 'count') {
|
||||
assert.ok(hasNullCategory, 'aggregation has not a category NULL');
|
||||
} else {
|
||||
assert.ok(!hasNullCategory, 'aggregation has category NULL');
|
||||
}
|
||||
|
||||
assert.ok(!hasNullCategory, 'aggregation has category NULL');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -425,3 +416,79 @@ describe('aggregation dataview tuned by categories query param', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('Count aggregation', function () {
|
||||
const mapConfig = {
|
||||
version: '1.5.0',
|
||||
layers: [
|
||||
{
|
||||
type: "cartodb",
|
||||
options: {
|
||||
source: {
|
||||
"id": "a0"
|
||||
},
|
||||
cartocss: "#points { marker-width: 10; marker-fill: red; }",
|
||||
cartocss_version: "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
dataviews: {
|
||||
categories: {
|
||||
source: {
|
||||
id: 'a0'
|
||||
},
|
||||
type: 'aggregation',
|
||||
options: {
|
||||
column: 'cat',
|
||||
aggregation: 'count'
|
||||
}
|
||||
}
|
||||
},
|
||||
analyses: [
|
||||
{
|
||||
id: "a0",
|
||||
type: "source",
|
||||
params: {
|
||||
query: `
|
||||
SELECT
|
||||
null::geometry the_geom_webmercator,
|
||||
CASE
|
||||
WHEN x % 4 = 0 THEN 1
|
||||
WHEN x % 4 = 1 THEN 2
|
||||
WHEN x % 4 = 2 THEN 3
|
||||
ELSE null
|
||||
END AS val,
|
||||
CASE
|
||||
WHEN x % 4 = 0 THEN 'category_1'
|
||||
WHEN x % 4 = 1 THEN 'category_2'
|
||||
WHEN x % 4 = 2 THEN 'category_3'
|
||||
ELSE null
|
||||
END AS cat
|
||||
FROM generate_series(1, 1000) x
|
||||
`
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
it(`should handle null values correctly when aggregationColumn isn't provided`, function (done) {
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview('categories', { own_filter: 0, categories: 0 }, (err, dataview) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(dataview.categories.length, 3);
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should handle null values correctly when aggregationColumn is provided`, function (done) {
|
||||
mapConfig.dataviews.categories.options.aggregationColumn = 'val';
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview('categories', { own_filter: 0, categories: 0 }, (err, dataview) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(dataview.categories.length, 3);
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -34,6 +34,48 @@ return function () {
|
||||
serverOptions.renderer.mvt.usePostGIS = originalUsePostGIS;
|
||||
});
|
||||
|
||||
describe('named map tile', function () {
|
||||
it('should get default named vector tile', function (done) {
|
||||
const apikeyToken = 1234;
|
||||
const templateName = `mvt-template-${usePostGIS ? 'postgis' : 'mapnik'}`;
|
||||
const template = {
|
||||
version: '0.0.1',
|
||||
name: templateName,
|
||||
placeholders: {
|
||||
buffersize: {
|
||||
type: 'number',
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
version: '1.7.0',
|
||||
layers: [{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: 'select * from populated_places_simple_reduced limit 10',
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0',
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const testClient = new TestClient(template, apikeyToken);
|
||||
|
||||
testClient.getNamedTile(templateName, 0, 0, 0, 'mvt', {}, (err, res, tile) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
const tileJSON = tile.toJSON();
|
||||
|
||||
assert.equal(tileJSON[0].features.length, 10);
|
||||
|
||||
testClient.drain(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('analysis-layers-dataviews-mvt', function () {
|
||||
|
||||
function createMapConfig(layers, dataviews, analysis) {
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('attributes', function() {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors);
|
||||
var msg = parsed.errors[0];
|
||||
assert.ok(msg.match(/0 features.*identified by fid -666/), msg);
|
||||
assert.equal(msg, "Multiple features (0) identified by 'i' = -666 in layer 1");
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
|
||||
@@ -27,6 +27,7 @@ module.exports = _.extend({}, serverOptions, {
|
||||
snapToGrid: false,
|
||||
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
|
||||
scale_factors: [1, 2],
|
||||
metrics: false,
|
||||
limits: {
|
||||
render: 0,
|
||||
cacheOnTimeout: true
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
@@ -131,6 +131,19 @@ HMSET rails:users:cartodb250user id ${TESTUSERID} \
|
||||
map_key 4321
|
||||
EOF
|
||||
|
||||
|
||||
# Remove this block when Auth fallback is not used anymore
|
||||
# AUTH_FALLBACK
|
||||
# A user to test auth fallback to no api keys mode
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
|
||||
HMSET rails:users:user_previous_to_project_auth id ${TESTUSERID} \
|
||||
database_name "${TEST_DB}" \
|
||||
database_host "localhost" \
|
||||
database_password "${TESTPASS}" \
|
||||
database_publicuser "${PUBLICUSER}"\
|
||||
map_key 4444
|
||||
EOF
|
||||
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 0
|
||||
HSET rails:${TEST_DB}:my_table infowindow "this, that, the other"
|
||||
HSET rails:${TEST_DB}:test_table_private_1 privacy "0"
|
||||
@@ -138,4 +151,77 @@ EOF
|
||||
|
||||
fi
|
||||
|
||||
# API keys ==============================
|
||||
|
||||
# User localhost -----------------------
|
||||
|
||||
# API Key Master
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
|
||||
HMSET api_keys:localhost:1234 \
|
||||
user "localhost" \
|
||||
type "master" \
|
||||
grants_sql "true" \
|
||||
grants_maps "true" \
|
||||
database_role "${TESTUSER}" \
|
||||
database_password "${TESTPASS}"
|
||||
EOF
|
||||
|
||||
# API Key Default public
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
|
||||
HMSET api_keys:localhost:default_public \
|
||||
user "localhost" \
|
||||
type "default" \
|
||||
grants_sql "true" \
|
||||
grants_maps "true" \
|
||||
database_role "test_windshaft_publicuser" \
|
||||
database_password "public"
|
||||
EOF
|
||||
|
||||
# API Key Regular
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
|
||||
HMSET api_keys:localhost:regular1 \
|
||||
user "localhost" \
|
||||
type "regular" \
|
||||
grants_sql "true" \
|
||||
grants_maps "true" \
|
||||
database_role "test_windshaft_regular1" \
|
||||
database_password "regular1"
|
||||
EOF
|
||||
|
||||
# API Key Regular 2 no Maps API access, only to check grants permissions to the API
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
|
||||
HMSET api_keys:localhost:regular2 \
|
||||
user "localhost" \
|
||||
type "regular" \
|
||||
grants_sql "true" \
|
||||
grants_maps "false" \
|
||||
database_role "test_windshaft_publicuser" \
|
||||
database_password "public"
|
||||
EOF
|
||||
|
||||
# User cartodb250user -----------------------
|
||||
|
||||
# API Key Master
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
|
||||
HMSET api_keys:cartodb250user:4321 \
|
||||
user "localhost" \
|
||||
type "master" \
|
||||
grants_sql "true" \
|
||||
grants_maps "true" \
|
||||
database_role "${TESTUSER}" \
|
||||
database_password "${TESTPASS}"
|
||||
EOF
|
||||
|
||||
# API Key Default
|
||||
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
|
||||
HMSET api_keys:cartodb250user:default_public \
|
||||
user "localhost" \
|
||||
type "default" \
|
||||
grants_sql "true" \
|
||||
grants_maps "true" \
|
||||
database_role "test_windshaft_publicuser" \
|
||||
database_password "public"
|
||||
EOF
|
||||
|
||||
|
||||
echo "Finished preparing data. Ready to run tests"
|
||||
|
||||
@@ -23,6 +23,12 @@ CREATE USER :PUBLICUSER WITH PASSWORD ':PUBLICPASS';
|
||||
DROP USER IF EXISTS :TESTUSER;
|
||||
CREATE USER :TESTUSER WITH PASSWORD ':TESTPASS';
|
||||
|
||||
-- regular user role 1
|
||||
DROP USER IF EXISTS test_windshaft_regular1;
|
||||
CREATE USER test_windshaft_regular1 WITH PASSWORD 'regular1';
|
||||
|
||||
GRANT test_windshaft_regular1 to :TESTUSER;
|
||||
|
||||
-- first table
|
||||
CREATE TABLE test_table (
|
||||
updated_at timestamp without time zone DEFAULT now(),
|
||||
@@ -189,6 +195,7 @@ 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;
|
||||
GRANT SELECT ON CDB_TableMetadata TO test_windshaft_regular1; -- for analysis. Warning: TBA
|
||||
|
||||
-- long name table
|
||||
CREATE TABLE
|
||||
@@ -412,6 +419,52 @@ INSERT INTO _vovw_1_test_special_float_values_table_overviews VALUES
|
||||
(3, 'El Rey del Tallarín', 'Plaza Conde de Toreno 2, Madrid, Spain', 'NaN'::float, '0101000020E610000021C8410933AD0DC0CB0EF10F5B364440', '0101000020110F000053E71AC64D3419C10F664E4659CC5241', 1),
|
||||
(4, 'El Lacón', 'Manuel Fernández y González 8, Madrid, Spain', 'infinity'::float, '0101000020E6100000BC5983F755990DC07D923B6C22354440', '0101000020110F00005DACDB056F2319C1EC41A980FCCA5241', 2);
|
||||
|
||||
-- auth tables --------------------------------------------
|
||||
|
||||
CREATE TABLE test_table_localhost_regular1 (
|
||||
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,
|
||||
CONSTRAINT enforce_dims_the_geom CHECK ((st_ndims(the_geom) = 2)),
|
||||
CONSTRAINT enforce_dims_the_geom_webmercator CHECK ((st_ndims(the_geom_webmercator) = 2)),
|
||||
CONSTRAINT enforce_geotype_the_geom CHECK (((geometrytype(the_geom) = 'POINT'::text) OR (the_geom IS NULL))),
|
||||
CONSTRAINT enforce_geotype_the_geom_webmercator CHECK (((geometrytype(the_geom_webmercator) = 'POINT'::text) OR (the_geom_webmercator IS NULL))),
|
||||
CONSTRAINT enforce_srid_the_geom CHECK ((st_srid(the_geom) = 4326)),
|
||||
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE test_table_localhost_regular1_cartodb_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE test_table_localhost_regular1_cartodb_id_seq OWNED BY test_table_localhost_regular1.cartodb_id;
|
||||
|
||||
SELECT pg_catalog.setval('test_table_localhost_regular1_cartodb_id_seq', 60, true);
|
||||
|
||||
ALTER TABLE test_table_localhost_regular1 ALTER COLUMN cartodb_id SET DEFAULT nextval('test_table_localhost_regular1_cartodb_id_seq'::regclass);
|
||||
|
||||
INSERT INTO test_table_localhost_regular1 VALUES
|
||||
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.314252', 1, 'Hawai', 'Calle de Pérez Galdós 9, Madrid, Spain', '0101000020E6100000A6B73F170D990DC064E8D84125364440', '0101000020110F000076491621312319C122D4663F1DCC5241'),
|
||||
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.319101', 2, 'El Estocolmo', 'Calle de la Palma 72, Madrid, Spain', '0101000020E6100000C90567F0F7AB0DC0AB07CC43A6364440', '0101000020110F0000C4356B29423319C15DD1092DADCC5241'),
|
||||
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.324', 3, 'El Rey del Tallarín', 'Plaza Conde de Toreno 2, Madrid, Spain', '0101000020E610000021C8410933AD0DC0CB0EF10F5B364440', '0101000020110F000053E71AC64D3419C10F664E4659CC5241'),
|
||||
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.329509', 4, 'El Lacón', 'Manuel Fernández y González 8, Madrid, Spain', '0101000020E6100000BC5983F755990DC07D923B6C22354440', '0101000020110F00005DACDB056F2319C1EC41A980FCCA5241'),
|
||||
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.334931', 5, 'El Pico', 'Calle Divino Pastor 12, Madrid, Spain', '0101000020E61000003B6D8D08C6A10DC0371B2B31CF364440', '0101000020110F00005F716E91992A19C17DAAA4D6DACC5241');
|
||||
|
||||
ALTER TABLE ONLY test_table_localhost_regular1 ADD CONSTRAINT test_table_localhost_regular1_pkey PRIMARY KEY (cartodb_id);
|
||||
|
||||
CREATE INDEX test_table_localhost_regular1_the_geom_idx ON test_table_localhost_regular1 USING gist (the_geom);
|
||||
CREATE INDEX test_table_localhost_regular1_the_geom_webmercator_idx ON test_table_localhost_regular1 USING gist (the_geom_webmercator);
|
||||
|
||||
GRANT ALL ON TABLE test_table_localhost_regular1 TO :TESTUSER;
|
||||
GRANT ALL ON TABLE test_table_localhost_regular1 TO test_windshaft_regular1;
|
||||
|
||||
-- analysis tables -----------------------------------------------
|
||||
|
||||
ALTER TABLE cdb_analysis_catalog OWNER TO :TESTUSER;
|
||||
@@ -705,6 +758,7 @@ GRANT SELECT ON TABLE analysis_rent_listings TO :PUBLICUSER;
|
||||
|
||||
--
|
||||
GRANT SELECT, UPDATE, INSERT, DELETE ON cdb_analysis_catalog TO :TESTUSER;
|
||||
GRANT SELECT, UPDATE, INSERT, DELETE ON cdb_analysis_catalog TO test_windshaft_regular1; -- for analysis. Warning: TBA
|
||||
|
||||
DROP EXTENSION IF EXISTS crankshaft;
|
||||
CREATE SCHEMA IF NOT EXISTS cdb_crankshaft;
|
||||
|
||||
@@ -414,7 +414,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
|
||||
var urlParams = {};
|
||||
if (params.hasOwnProperty('no_filters')) {
|
||||
urlParams.no_filters = params.no_filters;
|
||||
}
|
||||
}
|
||||
if (params.hasOwnProperty('own_filter')) {
|
||||
urlParams.own_filter = params.own_filter;
|
||||
}
|
||||
@@ -1253,3 +1253,87 @@ TestClient.prototype.getAnalysesCatalog = function (params, callback) {
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.getNamedTile = function (name, z, x, y, format, options, callback) {
|
||||
const { params } = options;
|
||||
|
||||
if (!this.apiKey) {
|
||||
return callback(new Error('apiKey param is mandatory to create a new template'));
|
||||
}
|
||||
|
||||
const createTemplateRequest = {
|
||||
url: `/api/v1/map/named?${qs.stringify({ api_key: this.apiKey })}`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(this.template)
|
||||
};
|
||||
|
||||
const createTemplateResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
assert.response(this.server, createTemplateRequest, createTemplateResponse, (res, err) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
const templateId = JSON.parse(res.body).template_id;
|
||||
const queryParams = params ? `?${qs.stringify(params)}` : '';
|
||||
const url = `/api/v1/map/named/${templateId}/all/${[z,x,y].join('/')}.${format}${queryParams}`;
|
||||
const namedTileRequest = {
|
||||
url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
};
|
||||
|
||||
let contentType;
|
||||
switch (format) {
|
||||
case 'png':
|
||||
contentType = 'image/png';
|
||||
break;
|
||||
case 'mvt':
|
||||
contentType = 'application/x-protobuf';
|
||||
break;
|
||||
default:
|
||||
contentType = 'application/json';
|
||||
break;
|
||||
}
|
||||
|
||||
const namedTileResponse = Object.assign({
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': contentType
|
||||
}
|
||||
}, options.response);
|
||||
|
||||
assert.response(this.server, namedTileRequest, namedTileResponse, (res, err) => {
|
||||
let body;
|
||||
switch (res.headers['content-type']) {
|
||||
case 'image/png':
|
||||
body = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
break;
|
||||
case 'application/x-protobuf':
|
||||
body = new mapnik.VectorTile(z, x, y);
|
||||
body.setDataSync(new Buffer(res.body, 'binary'));
|
||||
break;
|
||||
case 'application/json; charset=utf-8':
|
||||
body = JSON.parse(res.body);
|
||||
break;
|
||||
default:
|
||||
body = res.body;
|
||||
break;
|
||||
}
|
||||
|
||||
return callback(err, res, body);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -113,7 +113,14 @@ afterEach(function(done) {
|
||||
'rails:test_windshaft_cartodb_user_1_db:my_table': true,
|
||||
'rails:users:localhost:map_key': true,
|
||||
'rails:users:cartodb250user': true,
|
||||
'rails:users:localhost': true
|
||||
'rails:users:localhost': true,
|
||||
'rails:users:user_previous_to_project_auth': true, // AUTH_FALLBACK
|
||||
'api_keys:localhost:1234': true,
|
||||
'api_keys:localhost:default_public': true,
|
||||
'api_keys:cartodb250user:4321': true,
|
||||
'api_keys:cartodb250user:default_public': true,
|
||||
'api_keys:localhost:regular1': true,
|
||||
'api_keys:localhost:regular2': true,
|
||||
};
|
||||
var databasesTasks = { 0: 'users', 5: 'meta'};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('lzma-middleware', function() {
|
||||
}
|
||||
};
|
||||
testHelper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) {
|
||||
const lzma = lzmaMiddleware();
|
||||
var req = {
|
||||
headers: {
|
||||
host:'localhost'
|
||||
@@ -19,9 +20,13 @@ describe('lzma-middleware', function() {
|
||||
query: {
|
||||
api_key: 'test',
|
||||
lzma: data
|
||||
},
|
||||
profiler: {
|
||||
done: function () {}
|
||||
}
|
||||
};
|
||||
lzmaMiddleware(req, {}, function(err) {
|
||||
|
||||
lzma(req, {}, function(err) {
|
||||
if ( err ) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
require('../../../support/test_helper.js');
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var LayergroupController = require('../../../../lib/cartodb/controllers/layergroup');
|
||||
|
||||
describe('tile stats', function() {
|
||||
|
||||
beforeEach(function () {
|
||||
this.statsClient = global.statsClient;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
global.statsClient = this.statsClient;
|
||||
});
|
||||
|
||||
it('finalizeGetTileOrGrid does not call statsClient when format is not supported', function() {
|
||||
var expectedCalls = 2, // it will call increment once for the general error
|
||||
invalidFormat = 'png2',
|
||||
invalidFormatRegexp = new RegExp('invalid'),
|
||||
formatMatched = false;
|
||||
mockStatsClientGetInstance({
|
||||
increment: function(label) {
|
||||
formatMatched = formatMatched || !!label.match(invalidFormatRegexp);
|
||||
expectedCalls--;
|
||||
}
|
||||
});
|
||||
|
||||
var layergroupController = new LayergroupController();
|
||||
|
||||
var reqMock = {
|
||||
profiler: { toJSONString:function() {} },
|
||||
params: {
|
||||
format: invalidFormat
|
||||
}
|
||||
};
|
||||
var resMock = {
|
||||
status: function() { return this; },
|
||||
set: function() {},
|
||||
json: function() {},
|
||||
jsonp: function() {},
|
||||
send: function() {}
|
||||
};
|
||||
|
||||
var next = function () {};
|
||||
layergroupController.finalizeGetTileOrGrid('Unsupported format png2', reqMock, resMock, null, null, next);
|
||||
|
||||
assert.ok(formatMatched, 'Format was never matched in increment method');
|
||||
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');
|
||||
});
|
||||
|
||||
it('finalizeGetTileOrGrid calls statsClient when format is supported', function() {
|
||||
var expectedCalls = 2, // general error + format error
|
||||
validFormat = 'png',
|
||||
validFormatRegexp = new RegExp(validFormat),
|
||||
formatMatched = false;
|
||||
mockStatsClientGetInstance({
|
||||
increment: function(label) {
|
||||
formatMatched = formatMatched || !!label.match(validFormatRegexp);
|
||||
expectedCalls--;
|
||||
}
|
||||
});
|
||||
var reqMock = {
|
||||
profiler: { toJSONString:function() {} },
|
||||
params: {
|
||||
format: validFormat
|
||||
}
|
||||
};
|
||||
var resMock = {
|
||||
status: function() { return this; },
|
||||
set: function() {},
|
||||
json: function() {},
|
||||
jsonp: function() {},
|
||||
send: function() {}
|
||||
};
|
||||
|
||||
var layergroupController = new LayergroupController();
|
||||
|
||||
var next = function () {};
|
||||
layergroupController.finalizeGetTileOrGrid('Another error happened', reqMock, resMock, null, null, next);
|
||||
|
||||
assert.ok(formatMatched, 'Format was never matched in increment method');
|
||||
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');
|
||||
});
|
||||
|
||||
function mockStatsClientGetInstance(instance) {
|
||||
global.statsClient = Object.assign(global.statsClient, instance);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -10,6 +10,7 @@ var TemplateMaps = require('../../../lib/cartodb/backends/template_maps');
|
||||
const cleanUpQueryParamsMiddleware = require('../../../lib/cartodb/middleware/context/clean-up-query-params');
|
||||
const authorizeMiddleware = require('../../../lib/cartodb/middleware/context/authorize');
|
||||
const dbConnSetupMiddleware = require('../../../lib/cartodb/middleware/context/db-conn-setup');
|
||||
const credentialsMiddleware = require('../../../lib/cartodb/middleware/context/credentials');
|
||||
const localsMiddleware = require('../../../lib/cartodb/middleware/context/locals');
|
||||
|
||||
var windshaft = require('windshaft');
|
||||
@@ -23,6 +24,7 @@ describe('prepare-context', function() {
|
||||
let cleanUpQueryParams;
|
||||
let dbConnSetup;
|
||||
let authorize;
|
||||
let setCredentials;
|
||||
|
||||
before(function() {
|
||||
var redisPool = new RedisPool(global.environment.redis);
|
||||
@@ -35,6 +37,7 @@ describe('prepare-context', function() {
|
||||
cleanUpQueryParams = cleanUpQueryParamsMiddleware();
|
||||
authorize = authorizeMiddleware(authApi);
|
||||
dbConnSetup = dbConnSetupMiddleware(pgConnection);
|
||||
setCredentials = credentialsMiddleware();
|
||||
});
|
||||
|
||||
|
||||
@@ -58,20 +61,23 @@ describe('prepare-context', function() {
|
||||
}
|
||||
res.locals.user = 'localhost';
|
||||
|
||||
res.set = function () {};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
it('res.locals are created', function(done) {
|
||||
const locals = localsMiddleware();
|
||||
let req = {};
|
||||
let res = {};
|
||||
|
||||
localsMiddleware(prepareRequest(req), prepareResponse(res), function(err) {
|
||||
locals(prepareRequest(req), prepareResponse(res), function(err) {
|
||||
if ( err ) { done(err); return; }
|
||||
assert.ok(res.hasOwnProperty('locals'), 'response has locals');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('cleans up request', function(done){
|
||||
var req = {headers: { host:'localhost' }, query: {dbuser:'hacker',dbname:'secret'}};
|
||||
var res = {};
|
||||
@@ -103,8 +109,20 @@ describe('prepare-context', function() {
|
||||
});
|
||||
|
||||
it('sets also dbuser for authenticated requests', function(done){
|
||||
var req = { headers: { host: 'localhost' }, query: { map_key: '1234' }};
|
||||
var res = { set: function () {} };
|
||||
var req = {
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
query: {
|
||||
api_key: '1234'
|
||||
}
|
||||
};
|
||||
var res = {
|
||||
set: function () {},
|
||||
locals: {
|
||||
api_key: '1234'
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: review authorize-pgconnsetup workflow, It might we are doing authorization twice.
|
||||
authorize(prepareRequest(req), prepareResponse(res), function (err) {
|
||||
@@ -154,7 +172,7 @@ describe('prepare-context', function() {
|
||||
}
|
||||
};
|
||||
var res = {};
|
||||
|
||||
|
||||
cleanUpQueryParams(prepareRequest(req), prepareResponse(res), function (err) {
|
||||
if ( err ) {
|
||||
return done(err);
|
||||
@@ -168,4 +186,66 @@ describe('prepare-context', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Set apikey token', function(){
|
||||
it('from query param', function (done) {
|
||||
var req = {
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
query: {
|
||||
api_key: '1234',
|
||||
}
|
||||
};
|
||||
var res = {};
|
||||
setCredentials(prepareRequest(req), prepareResponse(res), function (err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
var query = res.locals;
|
||||
|
||||
assert.equal('1234', query.api_key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('from body param', function (done) {
|
||||
var req = {
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
body: {
|
||||
api_key: '1234',
|
||||
}
|
||||
};
|
||||
var res = {};
|
||||
setCredentials(prepareRequest(req), prepareResponse(res), function (err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
var query = res.locals;
|
||||
|
||||
assert.equal('1234', query.api_key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('from http header', function (done) {
|
||||
var req = {
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
authorization: 'Basic bG9jYWxob3N0OjEyMzQ=', // user: localhost, password: 1234
|
||||
}
|
||||
};
|
||||
var res = {};
|
||||
setCredentials(prepareRequest(req), prepareResponse(res), function (err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
var query = res.locals;
|
||||
|
||||
assert.equal('1234', query.api_key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
224
yarn.lock
224
yarn.lock
@@ -2,20 +2,20 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@carto/mapnik@3.6.2-carto.2", "@carto/mapnik@~3.6.2-carto.0":
|
||||
version "3.6.2-carto.2"
|
||||
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.2.tgz#45a055fd2d39530a873ef9ce5a325baacc81c196"
|
||||
"@carto/mapnik@3.6.2-carto.4":
|
||||
version "3.6.2-carto.4"
|
||||
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.4.tgz#54042a5dbea293c54e1bd286b32277694c5dc2d2"
|
||||
dependencies:
|
||||
mapnik-vector-tile "1.5.0"
|
||||
nan "~2.7.0"
|
||||
node-pre-gyp "~0.6.30"
|
||||
protozero "1.5.1"
|
||||
|
||||
"@carto/tilelive-bridge@github:cartodb/tilelive-bridge#2.5.1-cdb1":
|
||||
version "2.5.1-cdb1"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/b0b5559f948e77b337bc9a9ae0bf6ec4249fba21"
|
||||
"@carto/tilelive-bridge@cartodb/tilelive-bridge#2.5.1-cdb4":
|
||||
version "2.5.1-cdb4"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/3eb554e5109199f50f457cec72ee288cffa5d6b3"
|
||||
dependencies:
|
||||
"@carto/mapnik" "~3.6.2-carto.0"
|
||||
"@carto/mapnik" "3.6.2-carto.4"
|
||||
"@mapbox/sphericalmercator" "~1.0.1"
|
||||
mapnik-pool "~0.1.3"
|
||||
|
||||
@@ -23,19 +23,15 @@
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@mapbox/sphericalmercator/-/sphericalmercator-1.0.5.tgz#70237b9774095ed1cfdbcea7a8fd1fc82b2691f2"
|
||||
|
||||
"abaculus@github:cartodb/abaculus#2.0.3-cdb2":
|
||||
version "2.0.3-cdb2"
|
||||
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/6468e0e3fddb2b23f60b9a3156117cff0307f6dc"
|
||||
abaculus@cartodb/abaculus#2.0.3-cdb5:
|
||||
version "2.0.3-cdb5"
|
||||
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/b899cbea04b3e6093aa3ef32331920acd5f839a1"
|
||||
dependencies:
|
||||
"@carto/mapnik" "~3.6.2-carto.0"
|
||||
"@carto/mapnik" "3.6.2-carto.4"
|
||||
d3-queue "^2.0.2"
|
||||
sphericalmercator "1.0.x"
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
|
||||
abbrev@1.0.x:
|
||||
abbrev@1, abbrev@1.0.x:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
|
||||
|
||||
@@ -147,6 +143,12 @@ balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
|
||||
basic-auth@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba"
|
||||
dependencies:
|
||||
safe-buffer "5.1.1"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
|
||||
@@ -236,18 +238,18 @@ camelcase@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
|
||||
|
||||
camshaft@0.60.0:
|
||||
version "0.60.0"
|
||||
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.60.0.tgz#0433b5a576e08cabbc9bae1e1b22305274b8b7b6"
|
||||
camshaft@0.61.4:
|
||||
version "0.61.4"
|
||||
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.61.4.tgz#eeab7e69a07d6fbc39ebc01ad19acf979de8fb2a"
|
||||
dependencies:
|
||||
async "^1.5.2"
|
||||
bunyan "1.8.1"
|
||||
cartodb-psql "^0.10.1"
|
||||
debug "^3.1.0"
|
||||
dot "^1.0.3"
|
||||
request "^2.69.0"
|
||||
request "2.85.0"
|
||||
|
||||
"canvas@github:cartodb/node-canvas#1.6.2-cdb2":
|
||||
canvas@cartodb/node-canvas#1.6.2-cdb2:
|
||||
version "1.6.2-cdb2"
|
||||
resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766"
|
||||
dependencies:
|
||||
@@ -265,15 +267,15 @@ carto@0.16.3:
|
||||
semver "^5.1.0"
|
||||
yargs "^4.2.0"
|
||||
|
||||
"carto@github:cartodb/carto#0.15.1-cdb1":
|
||||
carto@CartoDB/carto#0.15.1-cdb1:
|
||||
version "0.15.1-cdb1"
|
||||
resolved "https://codeload.github.com/cartodb/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398"
|
||||
resolved "https://codeload.github.com/CartoDB/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398"
|
||||
dependencies:
|
||||
mapnik-reference "~6.0.2"
|
||||
optimist "~0.6.0"
|
||||
underscore "~1.6.0"
|
||||
|
||||
"carto@github:cartodb/carto#0.15.1-cdb3":
|
||||
carto@cartodb/carto#0.15.1-cdb3:
|
||||
version "0.15.1-cdb3"
|
||||
resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7"
|
||||
dependencies:
|
||||
@@ -299,12 +301,12 @@ cartodb-query-tables@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/cartodb-query-tables/-/cartodb-query-tables-0.3.0.tgz#56e18d869666eb2e8e2cb57d0baf3acc923f8756"
|
||||
|
||||
cartodb-redis@0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/cartodb-redis/-/cartodb-redis-0.14.0.tgz#6f82fdb3e5b7c8005dbaccd6172c1706c4378df2"
|
||||
cartodb-redis@0.16.0:
|
||||
version "0.16.0"
|
||||
resolved "https://registry.yarnpkg.com/cartodb-redis/-/cartodb-redis-0.16.0.tgz#969312fd329b24a76bf6e5a4dd961445f2eda734"
|
||||
dependencies:
|
||||
dot "~1.0.2"
|
||||
redis-mpool "~0.4.1"
|
||||
redis-mpool "^0.5.0"
|
||||
underscore "~1.6.0"
|
||||
|
||||
caseless@~0.12.0:
|
||||
@@ -549,14 +551,14 @@ domutils@1.5:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
dot@^1.0.3:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/dot/-/dot-1.1.2.tgz#c7377019fc4e550798928b2b9afeb66abfa1f2f9"
|
||||
|
||||
dot@~1.0.2:
|
||||
dot@^1.0.3, dot@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dot/-/dot-1.0.3.tgz#f8750bfb6b03c7664eb0e6cb1eb4c66419af9427"
|
||||
|
||||
double-ended-queue@^2.1.0-0:
|
||||
version "2.1.0-0"
|
||||
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
|
||||
|
||||
dtrace-provider@~0.6:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.6.0.tgz#0b078d5517937d873101452d9146737557b75e51"
|
||||
@@ -677,14 +679,10 @@ extend@~3.0.0, extend@~3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
extsprintf@1.3.0, extsprintf@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
|
||||
extsprintf@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
|
||||
fast-deep-equal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
|
||||
@@ -791,7 +789,7 @@ gdal@~0.9.2:
|
||||
nan "~2.6.2"
|
||||
node-pre-gyp "~0.6.36"
|
||||
|
||||
generic-pool@2.4.3:
|
||||
generic-pool@2.4.3, generic-pool@~2.4.0, generic-pool@~2.4.1:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff"
|
||||
|
||||
@@ -803,10 +801,6 @@ generic-pool@~2.2.0, generic-pool@~2.2.1:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.2.2.tgz#7a89f491d575b42f9f069a0e8e2c6dbaa3c241be"
|
||||
|
||||
generic-pool@~2.4.0, generic-pool@~2.4.1:
|
||||
version "2.4.6"
|
||||
resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.6.tgz#f1b55e572167dba2fe75d5aa91ebb1e9f72642d7"
|
||||
|
||||
get-caller-file@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
|
||||
@@ -1257,8 +1251,8 @@ lodash@3.7.x:
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45"
|
||||
|
||||
lodash@^4.5.1:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
version "4.17.5"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
||||
|
||||
log4js@cartodb/log4js-node#cdb:
|
||||
version "0.6.25"
|
||||
@@ -1347,9 +1341,9 @@ mime@~1.2.11:
|
||||
version "1.2.11"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10"
|
||||
|
||||
mime@~1.3.4:
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
|
||||
mime@~1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
|
||||
"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
|
||||
version "3.0.4"
|
||||
@@ -1357,7 +1351,7 @@ mime@~1.3.4:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@0.0.8:
|
||||
minimist@0.0.8, minimist@~0.0.1:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
@@ -1365,10 +1359,6 @@ minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
minimist@~0.0.1:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||
|
||||
minimist@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce"
|
||||
@@ -1395,11 +1385,7 @@ mocha@~3.4.1:
|
||||
mkdirp "0.5.1"
|
||||
supports-color "3.1.2"
|
||||
|
||||
moment@^2.10.6:
|
||||
version "2.20.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd"
|
||||
|
||||
moment@~2.18.1:
|
||||
moment@^2.10.6, moment@~2.18.1:
|
||||
version "2.18.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
|
||||
|
||||
@@ -1513,14 +1499,10 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
|
||||
|
||||
object-assign@4.1.0:
|
||||
object-assign@4.1.0, object-assign@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
|
||||
|
||||
object-assign@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
object-keys@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
|
||||
@@ -1649,7 +1631,7 @@ pg-types@1.*:
|
||||
postgres-date "~1.0.0"
|
||||
postgres-interval "^1.1.0"
|
||||
|
||||
"pg@github:cartodb/node-postgres#6.1.6-cdb1":
|
||||
pg@cartodb/node-postgres#6.1.6-cdb1:
|
||||
version "6.1.6"
|
||||
resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3eef52dd1e655f658a4ee8ac5697688b3ecfed44"
|
||||
dependencies:
|
||||
@@ -1817,7 +1799,7 @@ read-pkg@^1.0.0:
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^1.0.0"
|
||||
|
||||
readable-stream@1.1:
|
||||
readable-stream@1.1, readable-stream@~1.1.9:
|
||||
version "1.1.13"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"
|
||||
dependencies:
|
||||
@@ -1847,27 +1829,30 @@ readable-stream@~1.0.2:
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@~1.1.9:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
redis-commands@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b"
|
||||
|
||||
redis-mpool@0.4.1, redis-mpool@~0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/redis-mpool/-/redis-mpool-0.4.1.tgz#d917c0a4ed57a1291a9c6eb35434e6c0b7046f80"
|
||||
redis-mpool@0.5.0, redis-mpool@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-mpool/-/redis-mpool-0.5.0.tgz#9a22dcffb4ad796ec88ce3038b991deae9d1fda7"
|
||||
dependencies:
|
||||
generic-pool "~2.1.1"
|
||||
hiredis "~0.5.0"
|
||||
redis "~0.12.1"
|
||||
redis "^2.8.0"
|
||||
underscore "~1.6.0"
|
||||
|
||||
redis@~0.12.1:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/redis/-/redis-0.12.1.tgz#64df76ad0fc8acebaebd2a0645e8a48fac49185e"
|
||||
redis-parser@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"
|
||||
|
||||
redis@2.8.0, redis@^2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02"
|
||||
dependencies:
|
||||
double-ended-queue "^2.1.0-0"
|
||||
redis-commands "^1.2.0"
|
||||
redis-parser "^2.6.0"
|
||||
|
||||
repeat-string@^1.5.2:
|
||||
version "1.6.1"
|
||||
@@ -1900,7 +1885,34 @@ request@2.81.0:
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.0.0"
|
||||
|
||||
request@2.x, request@^2.55.0, request@^2.69.0, request@^2.83.0:
|
||||
request@2.85.0:
|
||||
version "2.85.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
|
||||
dependencies:
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.6.0"
|
||||
caseless "~0.12.0"
|
||||
combined-stream "~1.0.5"
|
||||
extend "~3.0.1"
|
||||
forever-agent "~0.6.1"
|
||||
form-data "~2.3.1"
|
||||
har-validator "~5.0.3"
|
||||
hawk "~6.0.2"
|
||||
http-signature "~1.2.0"
|
||||
is-typedarray "~1.0.0"
|
||||
isstream "~0.1.2"
|
||||
json-stringify-safe "~5.0.1"
|
||||
mime-types "~2.1.17"
|
||||
oauth-sign "~0.8.2"
|
||||
performance-now "^2.1.0"
|
||||
qs "~6.5.1"
|
||||
safe-buffer "^5.1.1"
|
||||
stringstream "~0.0.5"
|
||||
tough-cookie "~2.3.3"
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.1.0"
|
||||
|
||||
request@2.x, request@^2.55.0:
|
||||
version "2.83.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
|
||||
dependencies:
|
||||
@@ -1965,9 +1977,9 @@ safe-json-stringify@~1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
|
||||
semver@4.3.2:
|
||||
version "4.3.2"
|
||||
@@ -1981,10 +1993,6 @@ semver@~5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
|
||||
|
||||
semver@~5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
|
||||
send@0.16.1:
|
||||
version "0.16.1"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
|
||||
@@ -2082,14 +2090,10 @@ speedometer@~0.1.2:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d"
|
||||
|
||||
sphericalmercator@1.0.4:
|
||||
sphericalmercator@1.0.4, sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.4.tgz#baad4e34187f06e87f2e92fc1280199fa1b01d4e"
|
||||
|
||||
sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.5.tgz#ddc5a049e360e000d0fad9fc22c4071882584980"
|
||||
|
||||
split@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
|
||||
@@ -2127,11 +2131,7 @@ sshpk@^1.7.0:
|
||||
jsbn "~0.1.0"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
"statuses@>= 1.3.1 < 2":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
|
||||
|
||||
statuses@~1.3.1:
|
||||
"statuses@>= 1.3.1 < 2", statuses@~1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
|
||||
|
||||
@@ -2239,13 +2239,13 @@ through@2:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb4":
|
||||
version "0.6.18-cdb4"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/510cfb6f033f7551f973886751643202d4cb5f4a"
|
||||
tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb8:
|
||||
version "0.6.18-cdb8"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/9cb4546c8fdd34ced0a41dbf70e143475b4e2067"
|
||||
dependencies:
|
||||
"@carto/mapnik" "~3.6.2-carto.0"
|
||||
"@carto/mapnik" "3.6.2-carto.4"
|
||||
generic-pool "~2.4.0"
|
||||
mime "~1.3.4"
|
||||
mime "~1.6.0"
|
||||
sphericalmercator "~1.0.4"
|
||||
step "~0.0.5"
|
||||
|
||||
@@ -2400,13 +2400,13 @@ window-size@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
|
||||
|
||||
windshaft@4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.3.3.tgz#a48fdd6ca05257c103f34c80195722ef52dc0813"
|
||||
windshaft@4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.6.0.tgz#d9394aff73c0aa761207ad2b0f12d1c23ac41244"
|
||||
dependencies:
|
||||
"@carto/mapnik" "3.6.2-carto.2"
|
||||
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb1
|
||||
abaculus cartodb/abaculus#2.0.3-cdb2
|
||||
"@carto/mapnik" "3.6.2-carto.4"
|
||||
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb4
|
||||
abaculus cartodb/abaculus#2.0.3-cdb5
|
||||
canvas cartodb/node-canvas#1.6.2-cdb2
|
||||
carto cartodb/carto#0.15.1-cdb3
|
||||
cartodb-psql "^0.10.1"
|
||||
@@ -2414,13 +2414,13 @@ windshaft@4.3.3:
|
||||
dot "~1.0.2"
|
||||
grainstore "1.8.2"
|
||||
queue-async "~1.0.7"
|
||||
redis-mpool "0.4.1"
|
||||
request "^2.83.0"
|
||||
redis-mpool "^0.5.0"
|
||||
request "2.85.0"
|
||||
semver "~5.0.3"
|
||||
sphericalmercator "1.0.4"
|
||||
step "~0.0.6"
|
||||
tilelive "5.12.2"
|
||||
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb4
|
||||
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb8
|
||||
torque.js "~2.11.0"
|
||||
underscore "~1.6.0"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user