diff --git a/NEWS.md b/NEWS.md index 74bbe860..141e082e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/lib/cartodb/api/api-router.js b/lib/cartodb/api/api-router.js index 9d53285b..b7534559 100644 --- a/lib/cartodb/api/api-router.js +++ b/lib/cartodb/api/api-router.js @@ -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()); diff --git a/lib/cartodb/api/map/analyses-catalog-controller.js b/lib/cartodb/api/map/analyses-catalog-controller.js index 760c0e80..2fae5ac2 100644 --- a/lib/cartodb/api/map/analyses-catalog-controller.js +++ b/lib/cartodb/api/map/analyses-catalog-controller.js @@ -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 () { diff --git a/lib/cartodb/api/map/analysis-layergroup-controller.js b/lib/cartodb/api/map/analysis-layergroup-controller.js index 96f46d70..41acf819 100644 --- a/lib/cartodb/api/map/analysis-layergroup-controller.js +++ b/lib/cartodb/api/map/analysis-layergroup-controller.js @@ -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) - ); - + ]; } }; diff --git a/lib/cartodb/api/map/anonymous-map-controller.js b/lib/cartodb/api/map/anonymous-map-controller.js index 21049437..6bbacd37 100644 --- a/lib/cartodb/api/map/anonymous-map-controller.js +++ b/lib/cartodb/api/map/anonymous-map-controller.js @@ -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 () { diff --git a/lib/cartodb/api/map/attributes-layergroup-controller.js b/lib/cartodb/api/map/attributes-layergroup-controller.js index 632cca9f..2723ebd0 100644 --- a/lib/cartodb/api/map/attributes-layergroup-controller.js +++ b/lib/cartodb/api/map/attributes-layergroup-controller.js @@ -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() - ); + ]; } }; diff --git a/lib/cartodb/api/map/dataview-layergroup-controller.js b/lib/cartodb/api/map/dataview-layergroup-controller.js index 710c5cd4..5fd666ea 100644 --- a/lib/cartodb/api/map/dataview-layergroup-controller.js +++ b/lib/cartodb/api/map/dataview-layergroup-controller.js @@ -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() - ); + ]; } }; diff --git a/lib/cartodb/api/map/map-router.js b/lib/cartodb/api/map/map-router.js index 03f19937..46e2be5e 100644 --- a/lib/cartodb/api/map/map-router.js +++ b/lib/cartodb/api/map/map-router.js @@ -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); diff --git a/lib/cartodb/api/map/preview-layergroup-controller.js b/lib/cartodb/api/map/preview-layergroup-controller.js index 29db8729..975d3f56 100644 --- a/lib/cartodb/api/map/preview-layergroup-controller.js +++ b/lib/cartodb/api/map/preview-layergroup-controller.js @@ -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() - ); + ]; } }; diff --git a/lib/cartodb/api/map/preview-template-controller.js b/lib/cartodb/api/map/preview-template-controller.js index 9f5257a3..321bb882 100644 --- a/lib/cartodb/api/map/preview-template-controller.js +++ b/lib/cartodb/api/map/preview-template-controller.js @@ -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 }) { diff --git a/lib/cartodb/api/map/tile-layergroup-controller.js b/lib/cartodb/api/map/tile-layergroup-controller.js index f882d13c..311fec45 100644 --- a/lib/cartodb/api/map/tile-layergroup-controller.js +++ b/lib/cartodb/api/map/tile-layergroup-controller.js @@ -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() - ); + ]; } }; diff --git a/lib/cartodb/api/middlewares/check-static-image-format.js b/lib/cartodb/api/middlewares/check-static-image-format.js new file mode 100644 index 00000000..9b9c8b24 --- /dev/null +++ b/lib/cartodb/api/middlewares/check-static-image-format.js @@ -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(); + }; +}; diff --git a/lib/cartodb/api/middlewares/noop.js b/lib/cartodb/api/middlewares/noop.js new file mode 100644 index 00000000..16ceabbe --- /dev/null +++ b/lib/cartodb/api/middlewares/noop.js @@ -0,0 +1,5 @@ +module.exports = function noop () { + return function noopMiddleware (req, res, next) { + next(); + }; +}; diff --git a/lib/cartodb/api/template/admin-template-controller.js b/lib/cartodb/api/template/admin-template-controller.js index b45ebca7..b2087685 100644 --- a/lib/cartodb/api/template/admin-template-controller.js +++ b/lib/cartodb/api/template/admin-template-controller.js @@ -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); } diff --git a/lib/cartodb/api/template/named-template-controller.js b/lib/cartodb/api/template/named-template-controller.js index 86613c5e..0af1ad65 100644 --- a/lib/cartodb/api/template/named-template-controller.js +++ b/lib/cartodb/api/template/named-template-controller.js @@ -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 () { diff --git a/lib/cartodb/api/template/template-router.js b/lib/cartodb/api/template/template-router.js index 9ddcf9fc..4234bb22 100644 --- a/lib/cartodb/api/template/template-router.js +++ b/lib/cartodb/api/template/template-router.js @@ -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); diff --git a/lib/cartodb/api/template/tile-template-controller.js b/lib/cartodb/api/template/tile-template-controller.js index de256ccb..71a45e6e 100644 --- a/lib/cartodb/api/template/tile-template-controller.js +++ b/lib/cartodb/api/template/tile-template-controller.js @@ -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 }) { diff --git a/lib/cartodb/models/dataview/base.js b/lib/cartodb/models/dataview/base.js index 8c1ada5d..80e021b1 100644 --- a/lib/cartodb/models/dataview/base.js +++ b/lib/cartodb/models/dataview/base.js @@ -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); diff --git a/package.json b/package.json index 1387632c..e7350e01 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/test/acceptance/analysis/named-maps.js b/test/acceptance/analysis/named-maps.js index 42fb19d0..98316d2d 100644 --- a/test/acceptance/analysis/named-maps.js +++ b/test/acceptance/analysis/named-maps.js @@ -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() { diff --git a/test/acceptance/dataviews/aggregation.js b/test/acceptance/dataviews/aggregation.js index 204fcac7..9330b36f 100644 --- a/test/acceptance/dataviews/aggregation.js +++ b/test/acceptance/dataviews/aggregation.js @@ -491,4 +491,4 @@ describe('Count aggregation', function () { this.testClient.drain(done); }); }); -}); \ No newline at end of file +}); diff --git a/test/acceptance/dataviews/error-cases.js b/test/acceptance/dataviews/error-cases.js index 3e6cc2a8..78acba5d 100644 --- a/test/acceptance/dataviews/error-cases.js +++ b/test/acceptance/dataviews/error-cases.js @@ -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(); + }); + }); + }); }); diff --git a/test/acceptance/named_maps_static_view.js b/test/acceptance/named_maps_static_view.js index 9844c667..69c4e56c 100644 --- a/test/acceptance/named_maps_static_view.js +++ b/test/acceptance/named_maps_static_view.js @@ -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(); + }); + }); + }); + }); diff --git a/test/acceptance/regressions.js b/test/acceptance/regressions.js index 698d1401..e49065d3 100644 --- a/test/acceptance/regressions.js +++ b/test/acceptance/regressions.js @@ -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 = { diff --git a/yarn.lock b/yarn.lock index 02cc4826..57201e3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"