Merge branch 'master' into metadata

This commit is contained in:
Javier Goizueta 2018-05-18 20:49:26 +02:00
commit 8e8458e557
25 changed files with 932 additions and 558 deletions

12
NEWS.md
View File

@ -5,12 +5,18 @@ Released 2018-mm-dd
New features:
- CI tests with Ubuntu Xenial + PostgreSQL 10.1 and Ubuntu Precise + PostgreSQL 9.5
- Upgrades Windshaft to [4.7.3](https://github.com/CartoDB/Windshaft/blob/4.7.3/NEWS.md#version-473) which includes:
- A fix in mapnik-vector-tile to avoid grouping together properties with the same value but a different type.
- Performance improvements in the marker symbolizer (local cache, avoid building the collision matrix when possible).
- MVT: Disable simplify_distance to avoid multiple simplifications.
- Fix a bug with zero length lines not being rendered when using the marker symbolizer.
- Upgrades Camshaft to [0.61.9](https://github.com/CartoDB/camshaft/releases/tag/0.61.9):
- Use Dollar-Quoted String Constants to avoid Syntax Error while running moran analyses.
Bug Fixes:
- Validates tile coordinates (z/x/y) from request params to be a valid integer value.
- Upgrades Windshaft to 4.7.1, which includes a fix in mapnik-vector-tile to avoid grouping together properties with the same value but different type.
- Static maps fails for unsupported formats
- Handling errors extracting the column type on dataviews
## 6.1.0
Released 2018-04-16

View File

@ -191,7 +191,7 @@ module.exports = class ApiRouter {
Object.keys(this.serverOptions.routes).forEach(apiVersion => {
const routes = this.serverOptions.routes[apiVersion];
const apiRouter = router();
const apiRouter = router({ mergeParams: true });
apiRouter.use(logger(this.serverOptions));
apiRouter.use(initializeStatusCode());

View File

@ -8,29 +8,32 @@ const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const cacheControlHeader = require('../middlewares/cache-control-header');
const dbParamsFromResLocals = require('../../utils/database-params');
function AnalysesController(pgConnection, authBackend, userLimitsBackend) {
this.pgConnection = pgConnection;
this.authBackend = authBackend;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = class AnalysesController {
constructor (pgConnection, authBackend, userLimitsBackend) {
this.pgConnection = pgConnection;
this.authBackend = authBackend;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = AnalysesController;
register (mapRouter) {
mapRouter.get('/analyses/catalog', this.middlewares());
}
AnalysesController.prototype.register = function (mapRouter) {
mapRouter.get(
`/analyses/catalog`,
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ANALYSIS_CATALOG),
cleanUpQueryParams(),
createPGClient(),
getDataFromQuery({ queryTemplate: catalogQueryTpl, key: 'catalog' }),
getDataFromQuery({ queryTemplate: tablesQueryTpl, key: 'tables' }),
prepareResponse(),
cacheControlHeader({ ttl: 10, revalidate: true }),
unauthorizedError()
);
middlewares () {
return [
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ANALYSIS_CATALOG),
cleanUpQueryParams(),
createPGClient(),
getDataFromQuery({ queryTemplate: catalogQueryTpl, key: 'catalog' }),
getDataFromQuery({ queryTemplate: tablesQueryTpl, key: 'tables' }),
prepareResponse(),
cacheControlHeader({ ttl: 10, revalidate: true }),
unauthorizedError()
];
}
};
function createPGClient () {

View File

@ -16,8 +16,11 @@ module.exports = class AnalysisLayergroupController {
}
register (mapRouter) {
mapRouter.get(
`/:token/analysis/node/:nodeId`,
mapRouter.get('/:token/analysis/node/:nodeId', this.middlewares());
}
middlewares () {
return [
layergroupToken(),
credentials(),
authorize(this.authBackend),
@ -25,8 +28,7 @@ module.exports = class AnalysisLayergroupController {
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ANALYSIS),
cleanUpQueryParams(),
analysisNodeStatus(this.analysisStatusBackend)
);
];
}
};

View File

@ -22,88 +22,88 @@ const CreateLayergroupMapConfigProvider = require('../../models/mapconfig/provid
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
/**
* @param {AuthBackend} authBackend
* @param {PgConnection} pgConnection
* @param {TemplateMaps} templateMaps
* @param {MapBackend} mapBackend
* @param metadataBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsBackend} userLimitsBackend
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {MapConfigAdapter} mapConfigAdapter
* @param {StatsBackend} statsBackend
* @constructor
*/
function AnonymousMapController (
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
surrogateKeysCache,
userLimitsBackend,
layergroupAffectedTables,
mapConfigAdapter,
statsBackend,
authBackend,
layergroupMetadata
) {
this.pgConnection = pgConnection;
this.templateMaps = templateMaps;
this.mapBackend = mapBackend;
this.metadataBackend = metadataBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsBackend = userLimitsBackend;
this.layergroupAffectedTables = layergroupAffectedTables;
this.mapConfigAdapter = mapConfigAdapter;
this.statsBackend = statsBackend;
this.authBackend = authBackend;
this.layergroupMetadata = layergroupMetadata;
}
module.exports = class AnonymousMapController {
/**
* @param {AuthBackend} authBackend
* @param {PgConnection} pgConnection
* @param {TemplateMaps} templateMaps
* @param {MapBackend} mapBackend
* @param metadataBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsBackend} userLimitsBackend
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {MapConfigAdapter} mapConfigAdapter
* @param {StatsBackend} statsBackend
* @constructor
*/
constructor (
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
surrogateKeysCache,
userLimitsBackend,
layergroupAffectedTables,
mapConfigAdapter,
statsBackend,
authBackend,
layergroupMetadata
) {
this.pgConnection = pgConnection;
this.templateMaps = templateMaps;
this.mapBackend = mapBackend;
this.metadataBackend = metadataBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsBackend = userLimitsBackend;
this.layergroupAffectedTables = layergroupAffectedTables;
this.mapConfigAdapter = mapConfigAdapter;
this.statsBackend = statsBackend;
this.authBackend = authBackend;
this.layergroupMetadata = layergroupMetadata;
}
module.exports = AnonymousMapController;
register (mapRouter) {
mapRouter.options('/');
mapRouter.get('/', this.middlewares());
mapRouter.post('/', this.middlewares());
}
AnonymousMapController.prototype.register = function (mapRouter) {
mapRouter.options('/');
mapRouter.get('/', this.composeCreateMapMiddleware());
mapRouter.post('/', this.composeCreateMapMiddleware());
};
middlewares () {
const isTemplateInstantiation = false;
const useTemplateHash = false;
const includeQuery = true;
const label = 'ANONYMOUS LAYERGROUP';
const addContext = true;
AnonymousMapController.prototype.composeCreateMapMiddleware = function () {
const isTemplateInstantiation = false;
const useTemplateHash = false;
const includeQuery = true;
const label = 'ANONYMOUS LAYERGROUP';
const addContext = true;
return [
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS),
cleanUpQueryParams(['aggregation']),
initProfiler(isTemplateInstantiation),
checkJsonContentType(),
checkCreateLayergroup(),
prepareAdapterMapConfig(this.mapConfigAdapter),
createLayergroup (
this.mapBackend,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTables
),
incrementMapViewCount(this.metadataBackend),
augmentLayergroupData(),
cacheControlHeader({ ttl: global.environment.varnish.layergroupTtl || 86400, revalidate: true }),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader({ now: true }),
lastUpdatedTimeLayergroup(),
layerStats(this.pgConnection, this.statsBackend),
layergroupIdHeader(this.templateMaps, useTemplateHash),
layergroupMetadata(this.layergroupMetadata, includeQuery),
mapError({ label, addContext })
];
return [
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS),
cleanUpQueryParams(['aggregation']),
initProfiler(isTemplateInstantiation),
checkJsonContentType(),
checkCreateLayergroup(),
prepareAdapterMapConfig(this.mapConfigAdapter),
createLayergroup (
this.mapBackend,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTables
),
incrementMapViewCount(this.metadataBackend),
augmentLayergroupData(),
cacheControlHeader({ ttl: global.environment.varnish.layergroupTtl || 86400, revalidate: true }),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader({ now: true }),
lastUpdatedTimeLayergroup(),
layerStats(this.pgConnection, this.statsBackend),
layergroupIdHeader(this.templateMaps, useTemplateHash),
layergroupMetadata(this.layergroupMetadata, includeQuery),
mapError({ label, addContext })
];
}
};
function checkCreateLayergroup () {

View File

@ -31,8 +31,11 @@ module.exports = class AttributesLayergroupController {
}
register (mapRouter) {
mapRouter.get(
`/:token/:layer/attributes/:fid`,
mapRouter.get('/:token/:layer/attributes/:fid', this.middlewares());
}
middlewares () {
return [
layergroupToken(),
credentials(),
authorize(this.authBackend),
@ -50,7 +53,7 @@ module.exports = class AttributesLayergroupController {
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
);
];
}
};

View File

@ -49,76 +49,34 @@ module.exports = class DataviewLayergroupController {
// Undocumented/non-supported API endpoint methods.
// Use at your own peril.
mapRouter.get(
`/:token/dataview/:dataviewName`,
layergroupToken(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
getDataview(this.dataviewBackend),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
);
mapRouter.get('/:token/dataview/:dataviewName', this.middlewares({
action: 'get',
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW
}));
mapRouter.get(
`/:token/:layer/widget/:dataviewName`,
layergroupToken(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
getDataview(this.dataviewBackend),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
);
mapRouter.get('/:token/:layer/widget/:dataviewName', this.middlewares({
action: 'get',
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW
}));
mapRouter.get(
`/:token/dataview/:dataviewName/search`,
layergroupToken(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
dataviewSearch(this.dataviewBackend),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
);
mapRouter.get('/:token/dataview/:dataviewName/search', this.middlewares({
action: 'search',
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH
}));
mapRouter.get(
`/:token/:layer/widget/:dataviewName/search`,
mapRouter.get('/:token/:layer/widget/:dataviewName/search', this.middlewares({
action: 'search',
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH
}));
}
middlewares ({ action, rateLimitGroup }) {
return [
layergroupToken(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
rateLimit(this.userLimitsBackend, rateLimitGroup),
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
createMapStoreMapConfigProvider(
this.mapStore,
@ -126,12 +84,12 @@ module.exports = class DataviewLayergroupController {
this.pgConnection,
this.layergroupAffectedTablesCache
),
dataviewSearch(this.dataviewBackend),
action === 'search' ? dataviewSearch(this.dataviewBackend) : getDataview(this.dataviewBackend),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
);
];
}
};

View File

@ -113,7 +113,7 @@ module.exports = class MapRouter {
}
register (apiRouter, mapPaths) {
const mapRouter = router();
const mapRouter = router({ mergeParams: true });
this.analysisLayergroupController.register(mapRouter);
this.attributesLayergroupController.register(mapRouter);

View File

@ -2,6 +2,7 @@ const layergroupToken = require('../middlewares/layergroup-token');
const coordinates = require('../middlewares/coordinates');
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const noop = require('../middlewares/noop');
const dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const rateLimit = require('../middlewares/rate-limit');
@ -11,6 +12,7 @@ const cacheControlHeader = require('../middlewares/cache-control-header');
const cacheChannelHeader = require('../middlewares/cache-channel-header');
const surrogateKeyHeader = require('../middlewares/surrogate-key-header');
const lastModifiedHeader = require('../middlewares/last-modified-header');
const checkStaticImageFormat = require('../middlewares/check-static-image-format');
module.exports = class PreviewLayergroupController {
constructor (
@ -32,39 +34,39 @@ module.exports = class PreviewLayergroupController {
}
register (mapRouter) {
mapRouter.get('/static/center/:token/:z/:lat/:lng/:width/:height.:format', this.middlewares({
validateZoom: true,
previewType: 'centered'
}));
mapRouter.get('/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format', this.middlewares({
validateZoom: false,
previewType: 'bbox'
}));
}
middlewares ({ validateZoom, previewType }) {
const forcedFormat = 'png';
mapRouter.get(
`/static/center/:token/:z/:lat/:lng/:width/:height.:format`,
layergroupToken(),
coordinates({ z: true, x: false, y: false }),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC),
cleanUpQueryParams(['layer']),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache,
forcedFormat
),
getPreviewImageByCenter(this.previewBackend),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
);
let getPreviewImage;
mapRouter.get(
`/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`,
if (previewType === 'centered') {
getPreviewImage = getPreviewImageByCenter;
}
if (previewType === 'bbox') {
getPreviewImage = getPreviewImageByBoundingBox;
}
return [
layergroupToken(),
validateZoom ? coordinates({ z: true, x: false, y: false }) : noop(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC),
cleanUpQueryParams(['layer']),
checkStaticImageFormat(),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsBackend,
@ -72,12 +74,12 @@ module.exports = class PreviewLayergroupController {
this.layergroupAffectedTablesCache,
forcedFormat
),
getPreviewImageByBoundingBox(this.previewBackend),
getPreviewImage(this.previewBackend),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
);
];
}
};

View File

@ -7,6 +7,7 @@ const cacheControlHeader = require('../middlewares/cache-control-header');
const cacheChannelHeader = require('../middlewares/cache-channel-header');
const surrogateKeyHeader = require('../middlewares/surrogate-key-header');
const lastModifiedHeader = require('../middlewares/last-modified-header');
const checkStaticImageFormat = require('../middlewares/check-static-image-format');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
@ -22,54 +23,58 @@ function numMapper(n) {
return +n;
}
function PreviewTemplateController (
namedMapProviderCache,
previewBackend,
surrogateKeysCache,
tablesExtentBackend,
metadataBackend,
pgConnection,
authBackend,
userLimitsBackend
) {
this.namedMapProviderCache = namedMapProviderCache;
this.previewBackend = previewBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.tablesExtentBackend = tablesExtentBackend;
this.metadataBackend = metadataBackend;
this.pgConnection = pgConnection;
this.authBackend = authBackend;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = class PreviewTemplateController {
constructor (
namedMapProviderCache,
previewBackend,
surrogateKeysCache,
tablesExtentBackend,
metadataBackend,
pgConnection,
authBackend,
userLimitsBackend
) {
this.namedMapProviderCache = namedMapProviderCache;
this.previewBackend = previewBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.tablesExtentBackend = tablesExtentBackend;
this.metadataBackend = metadataBackend;
this.pgConnection = pgConnection;
this.authBackend = authBackend;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = PreviewTemplateController;
register (mapRouter) {
mapRouter.get('/static/named/:template_id/:width/:height.:format', this.middlewares());
}
PreviewTemplateController.prototype.register = function (mapRouter) {
mapRouter.get(
`/static/named/:template_id/:width/:height.:format`,
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC_NAMED),
cleanUpQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
namedMapProvider({
namedMapProviderCache: this.namedMapProviderCache,
label: 'STATIC_VIZ_MAP', forcedFormat: 'png'
}),
getTemplate({ label: 'STATIC_VIZ_MAP' }),
prepareLayerFilterFromPreviewLayers({
namedMapProviderCache: this.namedMapProviderCache,
label: 'STATIC_VIZ_MAP'
}),
getStaticImageOptions({ tablesExtentBackend: this.tablesExtentBackend }),
getImage({ previewBackend: this.previewBackend, label: 'STATIC_VIZ_MAP' }),
setContentTypeHeader(),
incrementMapViews({ metadataBackend: this.metadataBackend }),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
);
middlewares () {
return [
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC_NAMED),
cleanUpQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
checkStaticImageFormat(),
namedMapProvider({
namedMapProviderCache: this.namedMapProviderCache,
label: 'STATIC_VIZ_MAP', forcedFormat: 'png'
}),
getTemplate({ label: 'STATIC_VIZ_MAP' }),
prepareLayerFilterFromPreviewLayers({
namedMapProviderCache: this.namedMapProviderCache,
label: 'STATIC_VIZ_MAP'
}),
getStaticImageOptions({ tablesExtentBackend: this.tablesExtentBackend }),
getImage({ previewBackend: this.previewBackend, label: 'STATIC_VIZ_MAP' }),
setContentTypeHeader(),
incrementMapViews({ metadataBackend: this.metadataBackend }),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader()
];
}
};
function getTemplate ({ label }) {

View File

@ -42,14 +42,21 @@ module.exports = class TileLayergroupController {
}
register (mapRouter) {
// REGEXP doesn't match with `val`
// REGEXP: doesn't match with `val`
const not = (val) => `(?!${val})([^\/]+?)`;
// Sadly the path that matches 1 also matches with 2 so we need to tell to express
// that performs only the middlewares of the first path that matches
// for that we use one array to group all paths.
mapRouter.get([
`/:token/:z/:x/:y@:scale_factor?x.:format`,
`/:token/:z/:x/:y.:format`,
`/:token${not('static')}/:layer/:z/:x/:y.(:format)`
],
`/:token/:z/:x/:y@:scale_factor?x.:format`, // 1
`/:token/:z/:x/:y.:format`, // 2
`/:token${not('static')}/:layer/:z/:x/:y.(:format)`
], this.middlewares());
}
middlewares () {
return [
layergroupToken(),
coordinates(),
credentials(),
@ -72,7 +79,7 @@ module.exports = class TileLayergroupController {
incrementErrorMetrics(global.statsClient),
tileError(),
vectorError()
);
];
}
};

View File

@ -0,0 +1,11 @@
const VALID_IMAGE_FORMATS = ['png', 'jpg'];
module.exports = function checkStaticImageFormat () {
return function checkStaticImageFormatMiddleware (req, res, next) {
if(!VALID_IMAGE_FORMATS.includes(req.params.format)) {
return next(new Error(`Unsupported image format "${req.params.format}"`));
}
next();
};
};

View File

@ -0,0 +1,5 @@
module.exports = function noop () {
return function noopMiddleware (req, res, next) {
next();
};
};

View File

@ -3,70 +3,90 @@ const credentials = require('../middlewares/credentials');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
/**
* @param {AuthBackend} authBackend
* @param {PgConnection} pgConnection
* @param {TemplateMaps} templateMaps
* @constructor
*/
function AdminTemplateController(authBackend, templateMaps, userLimitsBackend) {
this.authBackend = authBackend;
this.templateMaps = templateMaps;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = class AdminTemplateController {
/**
* @param {AuthBackend} authBackend
* @param {PgConnection} pgConnection
* @param {TemplateMaps} templateMaps
* @constructor
*/
constructor (authBackend, templateMaps, userLimitsBackend) {
this.authBackend = authBackend;
this.templateMaps = templateMaps;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = AdminTemplateController;
register (templateRouter) {
templateRouter.options(`/:template_id`);
AdminTemplateController.prototype.register = function (templateRouter) {
templateRouter.options(`/:template_id`);
templateRouter.post('/', this.middlewares({
action: 'create',
label: 'POST TEMPLATE',
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_CREATE
}));
templateRouter.post(
`/`,
credentials(),
authorizedByAPIKey({ authBackend: this.authBackend, action: 'create', label: 'POST TEMPLATE' }),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_CREATE),
checkContentType({ action: 'POST', label: 'POST TEMPLATE' }),
createTemplate({ templateMaps: this.templateMaps })
);
templateRouter.put('/:template_id', this.middlewares({
action: 'update',
label: 'PUT TEMPLATE',
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_UPDATE
}));
templateRouter.put(
`/:template_id`,
credentials(),
authorizedByAPIKey({ authBackend: this.authBackend, action: 'update', label: 'PUT TEMPLATE' }),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_UPDATE),
checkContentType({ action: 'PUT', label: 'PUT TEMPLATE' }),
updateTemplate({ templateMaps: this.templateMaps })
);
templateRouter.get('/:template_id', this.middlewares({
action: 'get',
label: 'GET TEMPLATE',
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_GET
}));
templateRouter.get(
`/:template_id`,
credentials(),
authorizedByAPIKey({ authBackend: this.authBackend, action: 'get', label: 'GET TEMPLATE' }),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_GET),
retrieveTemplate({ templateMaps: this.templateMaps })
);
templateRouter.delete('/:template_id', this.middlewares({
action: 'delete',
label: 'DELETE TEMPLATE',
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_DELETE
}));
templateRouter.delete(
`/:template_id`,
credentials(),
authorizedByAPIKey({ authBackend: this.authBackend, action: 'delete', label: 'DELETE TEMPLATE' }),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_DELETE),
destroyTemplate({ templateMaps: this.templateMaps })
);
templateRouter.get('/', this.middlewares({
action: 'list',
label: 'GET TEMPLATE LIST',
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_LIST
}));
}
templateRouter.get(
`/`,
credentials(),
authorizedByAPIKey({ authBackend: this.authBackend, action: 'list', label: 'GET TEMPLATE LIST' }),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_LIST),
listTemplates({ templateMaps: this.templateMaps })
);
middlewares ({ action, label, rateLimitGroup }) {
let template;
if (action === 'create') {
template = createTemplate;
}
if (action === 'update') {
template = updateTemplate;
}
if (action === 'get') {
template = retrieveTemplate;
}
if (action === 'delete') {
template = destroyTemplate;
}
if (action === 'list') {
template = listTemplates;
}
return [
credentials(),
authorizedByAPIKey({ authBackend: this.authBackend, action, label }),
rateLimit(this.userLimitsBackend, rateLimitGroup),
checkContentType({ action: 'POST', label: 'POST TEMPLATE' }),
template({ templateMaps: this.templateMaps })
];
}
};
function checkContentType ({ action, label }) {
function checkContentType ({ 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`);
if ((req.method === 'POST' || req.method === 'PUT') && !req.is('application/json')) {
const error = new Error(`${req.method} template data must be of type application/json`);
error.label = label;
return next(error);
}

View File

@ -20,101 +20,95 @@ const CreateLayergroupMapConfigProvider = require('../../models/mapconfig/provid
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
/**
* @param {AuthBackend} authBackend
* @param {PgConnection} pgConnection
* @param {TemplateMaps} templateMaps
* @param {MapBackend} mapBackend
* @param metadataBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsBackend} userLimitsBackend
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {MapConfigAdapter} mapConfigAdapter
* @param {StatsBackend} statsBackend
* @constructor
*/
function NamedMapController (
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
surrogateKeysCache,
userLimitsBackend,
layergroupAffectedTables,
mapConfigAdapter,
statsBackend,
authBackend,
layergroupMetadata
) {
this.pgConnection = pgConnection;
this.templateMaps = templateMaps;
this.mapBackend = mapBackend;
this.metadataBackend = metadataBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsBackend = userLimitsBackend;
this.layergroupAffectedTables = layergroupAffectedTables;
this.mapConfigAdapter = mapConfigAdapter;
this.statsBackend = statsBackend;
this.authBackend = authBackend;
this.layergroupMetadata = layergroupMetadata;
}
module.exports = class NamedMapController {
/**
* @param {PgConnection} pgConnection
* @param {TemplateMaps} templateMaps
* @param {MapBackend} mapBackend
* @param metadataBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsBackend} userLimitsBackend
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {MapConfigAdapter} mapConfigAdapter
* @param {StatsBackend} statsBackend
* @param {AuthBackend} authBackend
* @param layergroupMetadata
* @constructor
*/
constructor (
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
surrogateKeysCache,
userLimitsBackend,
layergroupAffectedTables,
mapConfigAdapter,
statsBackend,
authBackend,
layergroupMetadata
) {
this.pgConnection = pgConnection;
this.templateMaps = templateMaps;
this.mapBackend = mapBackend;
this.metadataBackend = metadataBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsBackend = userLimitsBackend;
this.layergroupAffectedTables = layergroupAffectedTables;
this.mapConfigAdapter = mapConfigAdapter;
this.statsBackend = statsBackend;
this.authBackend = authBackend;
this.layergroupMetadata = layergroupMetadata;
}
module.exports = NamedMapController;
register (templateRouter) {
templateRouter.get('/:template_id/jsonp', this.middlewares());
templateRouter.post('/:template_id', this.middlewares());
}
NamedMapController.prototype.register = function (templateRouter) {
templateRouter.get(
`/:template_id/jsonp`,
this.composeInstantiateTemplateMiddleware()
);
middlewares () {
const isTemplateInstantiation = true;
const useTemplateHash = true;
const includeQuery = false;
const label = 'NAMED MAP LAYERGROUP';
const addContext = false;
templateRouter.post(
`/:template_id`,
this.composeInstantiateTemplateMiddleware()
);
};
NamedMapController.prototype.composeInstantiateTemplateMiddleware = function () {
const isTemplateInstantiation = true;
const useTemplateHash = true;
const includeQuery = false;
const label = 'NAMED MAP LAYERGROUP';
const addContext = false;
return [
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED),
cleanUpQueryParams(['aggregation']),
initProfiler(isTemplateInstantiation),
checkJsonContentType(),
checkInstantiteLayergroup(),
getTemplate(
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsBackend,
this.mapConfigAdapter,
this.layergroupAffectedTables
),
instantiateLayergroup(
this.mapBackend,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTables
),
incrementMapViewCount(this.metadataBackend),
augmentLayergroupData(),
cacheControlHeader({ ttl: global.environment.varnish.layergroupTtl || 86400, revalidate: true }),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader({ now: true }),
lastUpdatedTimeLayergroup(),
layerStats(this.pgConnection, this.statsBackend),
layergroupIdHeader(this.templateMaps ,useTemplateHash),
layergroupMetadata(this.layergroupMetadata, includeQuery),
mapError({ label, addContext })
];
return [
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED),
cleanUpQueryParams(['aggregation']),
initProfiler(isTemplateInstantiation),
checkJsonContentType(),
checkInstantiteLayergroup(),
getTemplate(
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsBackend,
this.mapConfigAdapter,
this.layergroupAffectedTables
),
instantiateLayergroup(
this.mapBackend,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTables
),
incrementMapViewCount(this.metadataBackend),
augmentLayergroupData(),
cacheControlHeader({ ttl: global.environment.varnish.layergroupTtl || 86400, revalidate: true }),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader({ now: true }),
lastUpdatedTimeLayergroup(),
layerStats(this.pgConnection, this.statsBackend),
layergroupIdHeader(this.templateMaps ,useTemplateHash),
layergroupMetadata(this.layergroupMetadata, includeQuery),
mapError({ label, addContext })
];
}
};
function checkInstantiteLayergroup () {

View File

@ -53,7 +53,7 @@ module.exports = class TemplateRouter {
}
register (apiRouter, templatePaths) {
const templateRouter = router();
const templateRouter = router({ mergeParams: true });
this.namedMapController.register(templateRouter);
this.tileTemplateController.register(templateRouter);

View File

@ -12,48 +12,51 @@ const vectorError = require('../middlewares/vector-error');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
function TileTemplateController (
namedMapProviderCache,
tileBackend,
surrogateKeysCache,
pgConnection,
authBackend,
userLimitsBackend
) {
this.namedMapProviderCache = namedMapProviderCache;
this.tileBackend = tileBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.pgConnection = pgConnection;
this.authBackend = authBackend;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = class TileTemplateController {
constructor (
namedMapProviderCache,
tileBackend,
surrogateKeysCache,
pgConnection,
authBackend,
userLimitsBackend
) {
this.namedMapProviderCache = namedMapProviderCache;
this.tileBackend = tileBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.pgConnection = pgConnection;
this.authBackend = authBackend;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = TileTemplateController;
register (templateRouter) {
templateRouter.get('/:template_id/:layer/:z/:x/:y.(:format)', this.middlewares());
}
TileTemplateController.prototype.register = function (templateRouter) {
templateRouter.get(
`/:template_id/:layer/:z/:x/:y.(:format)`,
coordinates(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_TILES),
cleanUpQueryParams(),
namedMapProvider({
namedMapProviderCache: this.namedMapProviderCache,
label: 'NAMED_MAP_TILE'
}),
getTile({
tileBackend: this.tileBackend,
label: 'NAMED_MAP_TILE'
}),
setContentTypeHeader(),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
vectorError()
);
middlewares () {
return [
coordinates(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_TILES),
cleanUpQueryParams(),
namedMapProvider({
namedMapProviderCache: this.namedMapProviderCache,
label: 'NAMED_MAP_TILE'
}),
getTile({
tileBackend: this.tileBackend,
label: 'NAMED_MAP_TILE'
}),
setContentTypeHeader(),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
vectorError()
];
}
};
function getTile ({ tileBackend, label }) {

View File

@ -52,6 +52,11 @@ module.exports = class BaseDataview {
if (err) {
return callback(err);
}
if (!result || !result.rows || !result.rows.length) {
return callback(new Error('The column type could not be determined'));
}
const pgType = result.rows[0].pg_typeof;
callback(null, getPGTypeName(pgType));
}, readOnlyTransaction);

View File

@ -26,7 +26,7 @@
"dependencies": {
"basic-auth": "^2.0.0",
"body-parser": "^1.18.2",
"camshaft": "0.61.8",
"camshaft": "0.61.9",
"cartodb-psql": "0.10.2",
"cartodb-query-tables": "0.3.0",
"cartodb-redis": "1.0.0",
@ -48,7 +48,7 @@
"step-profiler": "~0.3.0",
"turbo-carto": "0.20.2",
"underscore": "~1.6.0",
"windshaft": "4.7.1",
"windshaft": "4.7.3",
"yargs": "~5.0.0"
},
"devDependencies": {

View File

@ -261,6 +261,41 @@ describe('named-maps analysis', function() {
);
});
it('should return and an error requesting unsupported image format', function(done) {
assert.response(
server,
{
url: '/api/v1/map/static/center/' + layergroupid + '/4/42/-3/320/240.gif',
method: 'GET',
encoding: 'binary',
headers: {
host: username
}
},
{
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
},
function(res, err) {
assert.ifError(err);
assert.deepEqual(
JSON.parse(res.body),
{
errors:['Unsupported image format \"gif\"'],
errors_with_context:[{
type: 'unknown',
message: 'Unsupported image format \"gif\"'
}]
}
);
done();
}
);
});
});
describe('auto-instantiation', function() {

View File

@ -491,4 +491,4 @@ describe('Count aggregation', function () {
this.testClient.drain(done);
});
});
});
});

View File

@ -3,85 +3,211 @@ require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('histogram-dataview', function() {
describe('dataview error cases', function() {
describe('generic errors', function() {
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
var ERROR_RESPONSE = {
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
function createMapConfig(dataviews) {
return {
version: '1.5.0',
layers: [
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": "#points { marker-width: 10; marker-fill: red; }",
"cartocss_version": "2.3.0"
}
}
],
dataviews: dataviews,
analyses: [
{
"id": "HEAD",
"type": "source",
"params": {
"query": "select null::geometry the_geom_webmercator, x from generate_series(0,1000) x"
}
}
]
var ERROR_RESPONSE = {
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
}
it('should fail when invalid dataviews object is provided, string case', function(done) {
var mapConfig = createMapConfig("wadus-string");
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup({ response: ERROR_RESPONSE }, function(err, errObj) {
assert.ok(!err, err);
assert.deepEqual(errObj.errors, [ '"dataviews" must be a valid JSON object: "string" type found' ]);
done();
function createMapConfig(dataviews) {
return {
version: '1.5.0',
layers: [
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": "#points { marker-width: 10; marker-fill: red; }",
"cartocss_version": "2.3.0"
}
}
],
dataviews: dataviews,
analyses: [
{
"id": "HEAD",
"type": "source",
"params": {
"query": "select null::geometry the_geom_webmercator, x from generate_series(0,1000) x"
}
}
]
};
}
it('should fail when invalid dataviews object is provided, string case', function(done) {
var mapConfig = createMapConfig("wadus-string");
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup({ response: ERROR_RESPONSE }, function(err, errObj) {
assert.ok(!err, err);
assert.deepEqual(errObj.errors, [ '"dataviews" must be a valid JSON object: "string" type found' ]);
done();
});
});
it('should fail when invalid dataviews object is provided, array case', function(done) {
var mapConfig = createMapConfig([]);
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup({ response: ERROR_RESPONSE }, function(err, errObj) {
assert.ok(!err, err);
assert.deepEqual(errObj.errors, [ '"dataviews" must be a valid JSON object: "array" type found' ]);
done();
});
});
it('should work with empty but valid objects', function(done) {
var mapConfig = createMapConfig({});
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup(function(err, layergroup) {
assert.ok(!err, err);
assert.ok(layergroup);
assert.ok(layergroup.layergroupid);
done();
});
});
});
it('should fail when invalid dataviews object is provided, array case', function(done) {
var mapConfig = createMapConfig([]);
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup({ response: ERROR_RESPONSE }, function(err, errObj) {
assert.ok(!err, err);
describe('pg_typeof', function() {
assert.deepEqual(errObj.errors, [ '"dataviews" must be a valid JSON object: "array" type found' ]);
done();
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
});
it('should work with empty but valid objects', function(done) {
var mapConfig = createMapConfig({});
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup(function(err, layergroup) {
assert.ok(!err, err);
function createMapConfig(query) {
query = query || 'select * from populated_places_simple_reduced';
assert.ok(layergroup);
assert.ok(layergroup.layergroupid);
return {
version: '1.5.0',
layers: [
{
type: 'mapnik',
options: {
sql: query,
cartocss: '#layer0 { marker-fill: red; marker-width: 10; }',
cartocss_version: '2.0.1',
source: { id: "a0" }
}
}
],
analyses: [{
id: "a0",
type: "source",
params: {
query: query
}
}],
dataviews: {
aggregation_count_dataview: {
type: "aggregation",
source: { id: "a0" },
options: {
column: "adm0name",
aggregation: "count",
aggregationColumn: "adm0name"
}
}
}
};
}
done();
it('should work without filters', function(done) {
this.testClient = new TestClient(createMapConfig());
this.testClient.getDataview('aggregation_count_dataview', { own_filter: 0 }, function(err) {
assert.ifError(err);
done();
});
});
it('should work with filters', function(done) {
var params = {
filters: {
dataviews: {aggregation_count_dataview: {accept: ['Canada']}}
}
};
this.testClient = new TestClient(createMapConfig());
this.testClient.getDataview('aggregation_count_dataview', params, function(err) {
assert.ifError(err);
done();
});
});
it('should return an error if the column used by dataview does not exist', function(done) {
const query = 'select cartodb_id, the_geom, the_geom_webmercator from populated_places_simple_reduced';
const params = {
response: {
status: 404,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
}
};
const expectedResponseBody = {
errors:['column "adm0name" does not exist'],
errors_with_context:[{
type:'unknown',
message:'column "adm0name" does not exist'
}]
};
this.testClient = new TestClient(createMapConfig(query));
this.testClient.getDataview('aggregation_count_dataview', params, function(err, result) {
assert.ifError(err);
assert.deepEqual(result, expectedResponseBody);
done();
});
});
it('should not fail if query does not return rows', function(done) {
const query = 'select * from populated_places_simple_reduced limit 0';
const expectedResponseBody = {
aggregation: 'count',
count: 0,
nulls: 0,
nans: 0,
infinities: 0,
min: 0,
max: 0,
categoriesCount: 0,
categories: [],
type: 'aggregation'
};
this.testClient = new TestClient(createMapConfig(query));
this.testClient.getDataview('aggregation_count_dataview', { own_filter: 0 }, function(err, result) {
assert.ifError(err);
assert.deepEqual(result, expectedResponseBody);
done();
});
});
});
});

View File

@ -282,4 +282,57 @@ describe('named maps static view', function() {
});
});
it('should return an error requesting unsupported image format', function (done) {
var view = {
zoom: 4,
center: {
lng: 40,
lat: 20
}
};
templateMaps.addTemplate(username, createTemplate(view), function (err) {
if (err) {
return done(err);
}
var url = `/api/v1/map/static/named/${templateName}/640/480.gif`;
var requestOptions = {
url: url,
method: 'GET',
headers: {
host: username
},
encoding: 'binary'
};
var expectedResponse = {
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
// this could be removed once named maps are invalidated, otherwise you hits the cache
var server = new CartodbWindshaft(serverOptions);
assert.response(server, requestOptions, expectedResponse, function (res, err) {
assert.ifError(err);
assert.deepEqual(
JSON.parse(res.body),
{
errors:['Unsupported image format \"gif\"'],
errors_with_context:[{
type: 'unknown',
message: 'Unsupported image format \"gif\"'
}]
}
);
done();
});
});
});
});

View File

@ -1,7 +1,10 @@
require('../support/test_helper');
var assert = require('../support/assert');
const helper = require('../support/test_helper');
var TestClient = require('../support/test-client');
const LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
const CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
const serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
describe('regressions', function() {
@ -38,6 +41,49 @@ describe('regressions', function() {
});
});
// See: https://github.com/CartoDB/Windshaft-cartodb/pull/956
it('"/user/localhost/api/v1/map" should create an anonymous map', function (done) {
const server = new CartodbWindshaft(serverOptions);
const layergroup = {
version: '1.7.0',
layers: [
{
type: 'mapnik',
options: {
sql: TestClient.SQL.ONE_POINT,
cartocss: TestClient.CARTOCSS.POINTS,
cartocss_version: '2.3.0'
}
}
]
};
const keysToDelete = {};
assert.response(server,
{
url: '/user/localhost/api/v1/map',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(layergroup)
},
function(res, err) {
if (err) {
return done(err);
}
const body = JSON.parse(res.body);
assert.ok(body.layergroupid);
keysToDelete['map_cfg|' + LayergroupToken.parse(body.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
helper.deleteRedisKeys(keysToDelete, done);
}
);
});
describe('map instantiation', function () {
const apikeyToken = 'regular1';
const mapConfig = {

180
yarn.lock
View File

@ -2,20 +2,20 @@
# yarn lockfile v1
"@carto/mapnik@3.6.2-carto.8":
version "3.6.2-carto.8"
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.8.tgz#70448689d9b14d644bebd079f5714871c458a46d"
"@carto/mapnik@3.6.2-carto.10":
version "3.6.2-carto.10"
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.10.tgz#a97c951dcdac09d0eb35b3ea71e5eeaa206c1af6"
dependencies:
mapnik-vector-tile cartodb/mapnik-vector-tile#v1.6.1-carto.1
nan "2.10.0"
node-pre-gyp "0.7.0"
node-pre-gyp "0.10.0"
protozero "1.5.1"
"@carto/tilelive-bridge@cartodb/tilelive-bridge#2.5.1-cdb7":
version "2.5.1-cdb7"
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/ec881cb9ac52113f895f23857430e2d434bb99a6"
"@carto/tilelive-bridge@cartodb/tilelive-bridge#2.5.1-cdb9":
version "2.5.1-cdb9"
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/5129e43223cb55daed31373c7a36c98eb6178fc1"
dependencies:
"@carto/mapnik" "3.6.2-carto.8"
"@carto/mapnik" "3.6.2-carto.10"
"@mapbox/sphericalmercator" "~1.0.1"
mapnik-pool "~0.1.3"
@ -23,11 +23,11 @@
version "1.0.5"
resolved "https://registry.yarnpkg.com/@mapbox/sphericalmercator/-/sphericalmercator-1.0.5.tgz#70237b9774095ed1cfdbcea7a8fd1fc82b2691f2"
abaculus@cartodb/abaculus#2.0.3-cdb8:
version "2.0.3-cdb8"
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/31c03f2442943d4c47740fa154cda753b5cccd8a"
abaculus@cartodb/abaculus#2.0.3-cdb10:
version "2.0.3-cdb10"
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/90d537028bb8af8a35e7a40c46493066dd8a76b3"
dependencies:
"@carto/mapnik" "3.6.2-carto.8"
"@carto/mapnik" "3.6.2-carto.10"
d3-queue "^2.0.2"
sphericalmercator "1.0.x"
@ -238,9 +238,9 @@ camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
camshaft@0.61.8:
version "0.61.8"
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.61.8.tgz#75669c6c14791a93433e79a8892298e88cb0fce2"
camshaft@0.61.9:
version "0.61.9"
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.61.9.tgz#f3d399dfacf51b6a492c579e925c8b1141b22974"
dependencies:
async "^1.5.2"
bunyan "1.8.1"
@ -338,6 +338,10 @@ chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chownr@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
chroma-js@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-1.1.1.tgz#9bb9434959336ece75700aaadfeedc71806d8c05"
@ -455,7 +459,7 @@ debug@2.6.0:
dependencies:
ms "0.7.2"
debug@2.6.9, debug@^2.2.0:
debug@2.6.9, debug@^2.1.2, debug@^2.2.0:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
@ -551,7 +555,11 @@ domutils@1.5:
dom-serializer "0"
domelementtype "1"
dot@^1.0.3, dot@~1.0.2:
dot@^1.0.3:
version "1.1.2"
resolved "https://registry.yarnpkg.com/dot/-/dot-1.1.2.tgz#c7377019fc4e550798928b2b9afeb66abfa1f2f9"
dot@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/dot/-/dot-1.0.3.tgz#f8750bfb6b03c7664eb0e6cb1eb4c66419af9427"
@ -748,6 +756,12 @@ fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
fs-minipass@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
dependencies:
minipass "^2.2.1"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -1005,6 +1019,18 @@ iconv-lite@0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
iconv-lite@^0.4.4:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
dependencies:
safer-buffer ">= 2.1.2 < 3"
ignore-walk@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
dependencies:
minimatch "^3.0.4"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@ -1363,6 +1389,19 @@ minimist@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce"
minipass@^2.2.1, minipass@^2.2.4:
version "2.3.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.0.tgz#2e11b1c46df7fe7f1afbe9a490280add21ffe384"
dependencies:
safe-buffer "^5.1.1"
yallist "^3.0.0"
minizlib@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
dependencies:
minipass "^2.2.1"
mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@ -1385,7 +1424,11 @@ mocha@~3.4.1:
mkdirp "0.5.1"
supports-color "3.1.2"
moment@^2.10.6, moment@~2.18.1:
moment@^2.10.6:
version "2.22.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad"
moment@~2.18.1:
version "2.18.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
@ -1405,11 +1448,11 @@ mv@~2:
ncp "~2.0.0"
rimraf "~2.4.0"
nan@2.10.0:
nan@2.10.0, nan@^2.0.8:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
nan@^2.0.8, nan@^2.3.4, nan@^2.4.0:
nan@^2.3.4, nan@^2.4.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
@ -1429,6 +1472,14 @@ ncp@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
needle@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d"
dependencies:
debug "^2.1.2"
iconv-lite "^0.4.4"
sax "^1.2.4"
negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
@ -1444,20 +1495,20 @@ nock@~2.11.0:
mkdirp "^0.5.0"
propagate "0.3.x"
node-pre-gyp@0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.7.0.tgz#55aeffbaed93b50d0a4657d469198cd80ac9df36"
node-pre-gyp@0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46"
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
needle "^2.2.0"
nopt "^4.0.1"
npm-packlist "^1.1.6"
npmlog "^4.0.2"
rc "^1.1.7"
request "2.83.0"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^2.2.1"
tar-pack "^3.4.0"
tar "^4"
node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.36, node-pre-gyp@~0.6.38:
version "0.6.39"
@ -1501,6 +1552,17 @@ normalize-package-data@^2.3.2:
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
npm-bundled@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308"
npm-packlist@^1.1.6:
version "1.1.10"
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a"
dependencies:
ignore-walk "^3.0.1"
npm-bundled "^1.0.1"
npmlog@^4.0.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
@ -1904,9 +1966,9 @@ request@2.81.0:
tunnel-agent "^0.6.0"
uuid "^3.0.0"
request@2.83.0, request@2.x, request@^2.55.0:
version "2.83.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
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"
@ -1931,9 +1993,9 @@ request@2.83.0, request@2.x, request@^2.55.0:
tunnel-agent "^0.6.0"
uuid "^3.1.0"
request@2.85.0:
version "2.85.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
request@2.x, request@^2.55.0:
version "2.83.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.6.0"
@ -1992,9 +2054,21 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, s
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
safe-buffer@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
safe-json-stringify@~1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911"
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.1.0.tgz#bd2b6dad1ebafab3c24672a395527f01804b7e19"
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0:
version "5.3.0"
@ -2247,6 +2321,18 @@ tar@^2.2.1:
fstream "^1.0.2"
inherits "2"
tar@^4:
version "4.4.2"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.2.tgz#60685211ba46b38847b1ae7ee1a24d744a2cd462"
dependencies:
chownr "^1.0.1"
fs-minipass "^1.2.5"
minipass "^2.2.4"
minizlib "^1.1.0"
mkdirp "^0.5.0"
safe-buffer "^5.1.2"
yallist "^3.0.2"
through2@~0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f"
@ -2258,11 +2344,11 @@ through@2:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb12:
version "0.6.18-cdb12"
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/e3c0d80e604ca4a5dfad648ee6f6fb355d415a88"
tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb14:
version "0.6.18-cdb14"
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/6d06f728833d3e34d1adcd05567b3f4379f547bb"
dependencies:
"@carto/mapnik" "3.6.2-carto.8"
"@carto/mapnik" "3.6.2-carto.10"
generic-pool "~2.4.0"
mime "~1.6.0"
sphericalmercator "~1.0.4"
@ -2419,13 +2505,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.7.1:
version "4.7.1"
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.7.1.tgz#2b92753b2f6e97b239e15e1576ec312cc7dfeb13"
windshaft@4.7.3:
version "4.7.3"
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.7.3.tgz#86b6c9ae21f5ff9ad7a37bc344151e3d13da4f06"
dependencies:
"@carto/mapnik" "3.6.2-carto.8"
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb7
abaculus cartodb/abaculus#2.0.3-cdb8
"@carto/mapnik" "3.6.2-carto.10"
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb9
abaculus cartodb/abaculus#2.0.3-cdb10
canvas cartodb/node-canvas#1.6.2-cdb2
carto cartodb/carto#0.15.1-cdb3
cartodb-psql "^0.10.1"
@ -2439,7 +2525,7 @@ windshaft@4.7.1:
sphericalmercator "1.0.4"
step "~0.0.6"
tilelive "5.12.2"
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb12
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb14
torque.js "~2.11.0"
underscore "~1.6.0"
@ -2480,6 +2566,10 @@ y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
yallist@^3.0.0, yallist@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
yargs-parser@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4"