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 29cb11bc..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'); @@ -33,35 +34,33 @@ 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']), - checkStaticImageFormat(), - 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), @@ -75,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 1a292ddb..321bb882 100644 --- a/lib/cartodb/api/map/preview-template-controller.js +++ b/lib/cartodb/api/map/preview-template-controller.js @@ -23,55 +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']), - 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() - ); + 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/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/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 = {