diff --git a/lib/cartodb/controllers/map.js b/lib/cartodb/controllers/map/anonymous.js similarity index 69% rename from lib/cartodb/controllers/map.js rename to lib/cartodb/controllers/map/anonymous.js index f42d1b5d..9e269a60 100644 --- a/lib/cartodb/controllers/map.js +++ b/lib/cartodb/controllers/map/anonymous.js @@ -2,21 +2,20 @@ const _ = require('underscore'); const windshaft = require('windshaft'); const MapConfig = windshaft.model.MapConfig; const Datasource = windshaft.model.Datasource; -const ResourceLocator = require('../models/resource-locator'); -const cors = require('../middleware/cors'); -const cleanUpQueryParams = require('../middleware/clean-up-query-params'); -const credentials = require('../middleware/credentials'); -const dbConnSetup = require('../middleware/db-conn-setup'); -const authorize = require('../middleware/authorize'); -const cacheControlHeader = require('../middleware/cache-control-header'); -const cacheChannelHeader = require('../middleware/cache-channel-header'); -const surrogateKeyHeader = require('../middleware/surrogate-key-header'); -const lastModifiedHeader = require('../middleware/last-modified-header'); -const sendResponse = require('../middleware/send-response'); -const NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider'); -const CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/create-layergroup-provider'); -const LayergroupMetadata = require('../utils/layergroup-metadata'); -const rateLimit = require('../middleware/rate-limit'); +const ResourceLocator = require('../../models/resource-locator'); +const cors = require('../../middleware/cors'); +const cleanUpQueryParams = require('../../middleware/clean-up-query-params'); +const credentials = require('../../middleware/credentials'); +const dbConnSetup = require('../../middleware/db-conn-setup'); +const authorize = require('../../middleware/authorize'); +const cacheControlHeader = require('../../middleware/cache-control-header'); +const cacheChannelHeader = require('../../middleware/cache-channel-header'); +const surrogateKeyHeader = require('../../middleware/surrogate-key-header'); +const lastModifiedHeader = require('../../middleware/last-modified-header'); +const sendResponse = require('../../middleware/send-response'); +const CreateLayergroupMapConfigProvider = require('../../models/mapconfig/provider/create-layergroup-provider'); +const LayergroupMetadata = require('../../utils/layergroup-metadata'); +const rateLimit = require('../../middleware/rate-limit'); const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit; /** @@ -32,7 +31,7 @@ const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit; * @param {StatsBackend} statsBackend * @constructor */ -function MapController ( +function AnonymousMapController ( pgConnection, templateMaps, mapBackend, @@ -60,42 +59,37 @@ function MapController ( this.authApi = authApi; } -module.exports = MapController; +module.exports = AnonymousMapController; -MapController.prototype.register = function(mapRouter, templateRouter) { - mapRouter.get(`/`, this.composeCreateMapMiddleware(RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS)); - mapRouter.post(`/`, this.composeCreateMapMiddleware(RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS)); +AnonymousMapController.prototype.register = function (mapRouter) { + mapRouter.get(`/`, this.composeCreateMapMiddleware()); + mapRouter.post(`/`, this.composeCreateMapMiddleware()); mapRouter.options(`/`, cors('Content-Type')); - - const useTemplate = true; - - templateRouter.get( - `/:template_id/jsonp`, - this.composeCreateMapMiddleware(RATE_LIMIT_ENDPOINTS_GROUPS.NAMED, useTemplate) - ); - - templateRouter.post( - `/:template_id`, - this.composeCreateMapMiddleware(RATE_LIMIT_ENDPOINTS_GROUPS.NAMED, useTemplate) - ); }; -MapController.prototype.composeCreateMapMiddleware = function (endpointGroup, useTemplate = false) { - const isTemplateInstantiation = useTemplate; - const useTemplateHash = useTemplate; - const includeQuery = !useTemplate; - const label = useTemplate ? 'NAMED MAP LAYERGROUP' : 'ANONYMOUS LAYERGROUP'; - const addContext = !useTemplate; +AnonymousMapController.prototype.composeCreateMapMiddleware = function () { + const isTemplateInstantiation = false; + const useTemplateHash = false; + const includeQuery = true; + const label = 'ANONYMOUS LAYERGROUP'; + const addContext = true; return [ credentials(), authorize(this.authApi), dbConnSetup(this.pgConnection), - rateLimit(this.userLimitsApi, endpointGroup), + rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS), cleanUpQueryParams(['aggregation']), initProfiler(isTemplateInstantiation), checkJsonContentType(), - this.getCreateMapMiddlewares(useTemplate), + checkCreateLayergroup(), + prepareAdapterMapConfig(this.mapConfigAdapter), + createLayergroup ( + this.mapBackend, + this.userLimitsApi, + this.pgConnection, + this.layergroupAffectedTables + ), incrementMapViewCount(this.metadataBackend), augmentLayergroupData(), cacheControlHeader({ ttl: global.environment.varnish.layergroupTtl || 86400, revalidate: true }), @@ -115,39 +109,6 @@ MapController.prototype.composeCreateMapMiddleware = function (endpointGroup, us ]; }; -MapController.prototype.getCreateMapMiddlewares = function (useTemplate) { - if (useTemplate) { - return [ - checkInstantiteLayergroup(), - getTemplate( - this.templateMaps, - this.pgConnection, - this.metadataBackend, - this.userLimitsApi, - this.mapConfigAdapter, - this.layergroupAffectedTables - ), - instantiateLayergroup( - this.mapBackend, - this.userLimitsApi, - this.pgConnection, - this.layergroupAffectedTables - ) - ]; - } - - return [ - checkCreateLayergroup(), - prepareAdapterMapConfig(this.mapConfigAdapter), - createLayergroup ( - this.mapBackend, - this.userLimitsApi, - this.pgConnection, - this.layergroupAffectedTables - ) - ]; -}; - function initProfiler (isTemplateInstantiation) { const operation = isTemplateInstantiation ? 'instance_template' : 'createmap'; @@ -170,30 +131,6 @@ function checkJsonContentType () { }; } -function checkInstantiteLayergroup () { - return function checkInstantiteLayergroupMiddleware(req, res, next) { - if (req.method === 'GET') { - const { callback, config } = req.query; - - if (callback === undefined || callback.length === 0) { - return next(new Error('callback parameter should be present and be a function name')); - } - - if (config) { - try { - req.body = JSON.parse(config); - } catch(e) { - return next(new Error('Invalid config parameter, should be a valid JSON')); - } - } - } - - req.profiler.done('checkInstantiteLayergroup'); - - return next(); - }; -} - function checkCreateLayergroup () { return function checkCreateLayergroupMiddleware (req, res, next) { if (req.method === 'GET') { @@ -215,51 +152,6 @@ function checkCreateLayergroup () { }; } -function getTemplate ( - templateMaps, - pgConnection, - metadataBackend, - userLimitsApi, - mapConfigAdapter, - affectedTablesCache -) { - return function getTemplateMiddleware (req, res, next) { - const templateParams = req.body; - const { user, dbuser, dbname, dbpassword, dbhost, dbport } = res.locals; - const { template_id } = req.params; - const { auth_token } = req.query; - - const params = { dbuser, dbname, dbpassword, dbhost, dbport }; - - const mapConfigProvider = new NamedMapMapConfigProvider( - templateMaps, - pgConnection, - metadataBackend, - userLimitsApi, - mapConfigAdapter, - affectedTablesCache, - user, - template_id, - templateParams, - auth_token, - params - ); - - mapConfigProvider.getMapConfig((err, mapConfig, rendererParams) => { - req.profiler.done('named.getMapConfig'); - if (err) { - return next(err); - } - - res.locals.mapConfig = mapConfig; - res.locals.rendererParams = rendererParams; - res.locals.mapConfigProvider = mapConfigProvider; - - next(); - }); - }; -} - function prepareAdapterMapConfig (mapConfigAdapter) { return function prepareAdapterMapConfigMiddleware(req, res, next) { const requestMapConfig = req.body; @@ -343,37 +235,6 @@ function createLayergroup (mapBackend, userLimitsApi, pgConnection, affectedTabl }; } -function instantiateLayergroup (mapBackend, userLimitsApi, pgConnection, affectedTablesCache) { - return function instantiateLayergroupMiddleware (req, res, next) { - const { user, mapConfig, rendererParams } = res.locals; - const mapConfigProvider = new CreateLayergroupMapConfigProvider( - mapConfig, - user, - userLimitsApi, - pgConnection, - affectedTablesCache, - rendererParams - ); - - mapBackend.createLayergroup(mapConfig, rendererParams, mapConfigProvider, (err, layergroup) => { - req.profiler.done('createLayergroup'); - if (err) { - return next(err); - } - - res.body = layergroup; - - const { mapConfigProvider } = res.locals; - - res.locals.analysesResults = mapConfigProvider.analysesResults; - res.locals.template = mapConfigProvider.template; - res.locals.context = mapConfigProvider.context; - - next(); - }); - }; -} - function incrementMapViewCount (metadataBackend) { return function incrementMapViewCountMiddleware(req, res, next) { const { mapConfig, user } = res.locals; diff --git a/lib/cartodb/controllers/map/index.js b/lib/cartodb/controllers/map/index.js new file mode 100644 index 00000000..70d45fe9 --- /dev/null +++ b/lib/cartodb/controllers/map/index.js @@ -0,0 +1,60 @@ +const AnonymousMapController = require('./anonymous'); +const NamedMapController = require('./named'); + +module.exports = class MapController { + constructor ( + pgConnection, + templateMaps, + mapBackend, + metadataBackend, + surrogateKeysCache, + userLimitsApi, + layergroupAffectedTablesCache, + mapConfigAdapter, + statsBackend, + authApi + ) { + this.pgConnection = pgConnection; + this.templateMaps = templateMaps; + this.mapBackend = mapBackend; + this.metadataBackend = metadataBackend; + this.surrogateKeysCache = surrogateKeysCache; + this.userLimitsApi = userLimitsApi; + this.layergroupAffectedTablesCache = layergroupAffectedTablesCache; + this.mapConfigAdapter = mapConfigAdapter; + this.statsBackend = statsBackend; + this.authApi = authApi; + } + + register (mapRouter, templateRouter) { + const anonymousMapController = new AnonymousMapController( + this.pgConnection, + this.templateMaps, + this.mapBackend, + this.metadataBackend, + this.surrogateKeysCache, + this.userLimitsApi, + this.layergroupAffectedTablesCache, + this.mapConfigAdapter, + this.statsBackend, + this.authApi + ); + + anonymousMapController.register(mapRouter); + + const namedMapController = new NamedMapController( + this.pgConnection, + this.templateMaps, + this.mapBackend, + this.metadataBackend, + this.surrogateKeysCache, + this.userLimitsApi, + this.layergroupAffectedTablesCache, + this.mapConfigAdapter, + this.statsBackend, + this.authApi + ); + + namedMapController.register(templateRouter); + } +}; diff --git a/lib/cartodb/controllers/map/named.js b/lib/cartodb/controllers/map/named.js new file mode 100644 index 00000000..9f4c6433 --- /dev/null +++ b/lib/cartodb/controllers/map/named.js @@ -0,0 +1,446 @@ +const _ = require('underscore'); +const ResourceLocator = require('../../models/resource-locator'); +const cleanUpQueryParams = require('../../middleware/clean-up-query-params'); +const credentials = require('../../middleware/credentials'); +const dbConnSetup = require('../../middleware/db-conn-setup'); +const authorize = require('../../middleware/authorize'); +const cacheControlHeader = require('../../middleware/cache-control-header'); +const cacheChannelHeader = require('../../middleware/cache-channel-header'); +const surrogateKeyHeader = require('../../middleware/surrogate-key-header'); +const lastModifiedHeader = require('../../middleware/last-modified-header'); +const sendResponse = require('../../middleware/send-response'); +const NamedMapMapConfigProvider = require('../../models/mapconfig/provider/named-map-provider'); +const CreateLayergroupMapConfigProvider = require('../../models/mapconfig/provider/create-layergroup-provider'); +const LayergroupMetadata = require('../../utils/layergroup-metadata'); +const rateLimit = require('../../middleware/rate-limit'); +const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit; + +/** + * @param {AuthApi} authApi + * @param {PgConnection} pgConnection + * @param {TemplateMaps} templateMaps + * @param {MapBackend} mapBackend + * @param metadataBackend + * @param {SurrogateKeysCache} surrogateKeysCache + * @param {UserLimitsApi} userLimitsApi + * @param {LayergroupAffectedTables} layergroupAffectedTables + * @param {MapConfigAdapter} mapConfigAdapter + * @param {StatsBackend} statsBackend + * @constructor + */ +function NamedMapController ( + pgConnection, + templateMaps, + mapBackend, + metadataBackend, + surrogateKeysCache, + userLimitsApi, + layergroupAffectedTables, + mapConfigAdapter, + statsBackend, + authApi +) { + this.pgConnection = pgConnection; + this.templateMaps = templateMaps; + this.mapBackend = mapBackend; + this.metadataBackend = metadataBackend; + this.surrogateKeysCache = surrogateKeysCache; + this.userLimitsApi = userLimitsApi; + this.layergroupAffectedTables = layergroupAffectedTables; + + this.mapConfigAdapter = mapConfigAdapter; + const resourceLocator = new ResourceLocator(global.environment); + this.layergroupMetadata = new LayergroupMetadata(resourceLocator); + + this.statsBackend = statsBackend; + this.authApi = authApi; +} + +module.exports = NamedMapController; + +NamedMapController.prototype.register = function (templateRouter) { + templateRouter.get( + `/:template_id/jsonp`, + this.composeInstantiateTemplateMiddleware() + ); + + 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.authApi), + dbConnSetup(this.pgConnection), + rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED), + cleanUpQueryParams(['aggregation']), + initProfiler(isTemplateInstantiation), + checkJsonContentType(), + checkInstantiteLayergroup(), + getTemplate( + this.templateMaps, + this.pgConnection, + this.metadataBackend, + this.userLimitsApi, + this.mapConfigAdapter, + this.layergroupAffectedTables + ), + instantiateLayergroup( + this.mapBackend, + this.userLimitsApi, + 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 }), + setLastUpdatedTimeToLayergroup(), + setLayerStats(this.pgConnection, this.statsBackend), + setLayergroupIdHeader(this.templateMaps ,useTemplateHash), + setDataviewsAndWidgetsUrlsToLayergroupMetadata(this.layergroupMetadata), + setAnalysesMetadataToLayergroup(this.layergroupMetadata, includeQuery), + setTurboCartoMetadataToLayergroup(this.layergroupMetadata), + setAggregationMetadataToLayergroup(this.layergroupMetadata), + setTilejsonMetadataToLayergroup(this.layergroupMetadata), + sendResponse(), + augmentError({ label, addContext }) + ]; +}; + +function initProfiler (isTemplateInstantiation) { + const operation = isTemplateInstantiation ? 'instance_template' : 'createmap'; + + return function initProfilerMiddleware (req, res, next) { + req.profiler.start(`windshaft-cartodb.${operation}_${req.method.toLowerCase()}`); + req.profiler.done(`${operation}.initProfilerMiddleware`); + next(); + }; +} + +function checkJsonContentType () { + return function checkJsonContentTypeMiddleware(req, res, next) { + if (req.method === 'POST' && !req.is('application/json')) { + return next(new Error('POST data must be of type application/json')); + } + + req.profiler.done('checkJsonContentTypeMiddleware'); + + next(); + }; +} + +function checkInstantiteLayergroup () { + return function checkInstantiteLayergroupMiddleware(req, res, next) { + if (req.method === 'GET') { + const { callback, config } = req.query; + + if (callback === undefined || callback.length === 0) { + return next(new Error('callback parameter should be present and be a function name')); + } + + if (config) { + try { + req.body = JSON.parse(config); + } catch(e) { + return next(new Error('Invalid config parameter, should be a valid JSON')); + } + } + } + + req.profiler.done('checkInstantiteLayergroup'); + + return next(); + }; +} + +function getTemplate ( + templateMaps, + pgConnection, + metadataBackend, + userLimitsApi, + mapConfigAdapter, + affectedTablesCache +) { + return function getTemplateMiddleware (req, res, next) { + const templateParams = req.body; + const { user, dbuser, dbname, dbpassword, dbhost, dbport } = res.locals; + const { template_id } = req.params; + const { auth_token } = req.query; + + const params = { dbuser, dbname, dbpassword, dbhost, dbport }; + + const mapConfigProvider = new NamedMapMapConfigProvider( + templateMaps, + pgConnection, + metadataBackend, + userLimitsApi, + mapConfigAdapter, + affectedTablesCache, + user, + template_id, + templateParams, + auth_token, + params + ); + + mapConfigProvider.getMapConfig((err, mapConfig, rendererParams) => { + req.profiler.done('named.getMapConfig'); + if (err) { + return next(err); + } + + res.locals.mapConfig = mapConfig; + res.locals.rendererParams = rendererParams; + res.locals.mapConfigProvider = mapConfigProvider; + + next(); + }); + }; +} + +function instantiateLayergroup (mapBackend, userLimitsApi, pgConnection, affectedTablesCache) { + return function instantiateLayergroupMiddleware (req, res, next) { + const { user, mapConfig, rendererParams } = res.locals; + const mapConfigProvider = new CreateLayergroupMapConfigProvider( + mapConfig, + user, + userLimitsApi, + pgConnection, + affectedTablesCache, + rendererParams + ); + + mapBackend.createLayergroup(mapConfig, rendererParams, mapConfigProvider, (err, layergroup) => { + req.profiler.done('createLayergroup'); + if (err) { + return next(err); + } + + res.body = layergroup; + + const { mapConfigProvider } = res.locals; + + res.locals.analysesResults = mapConfigProvider.analysesResults; + res.locals.template = mapConfigProvider.template; + res.locals.context = mapConfigProvider.context; + + next(); + }); + }; +} + +function incrementMapViewCount (metadataBackend) { + return function incrementMapViewCountMiddleware(req, res, next) { + const { mapConfig, user } = res.locals; + + // Error won't blow up, just be logged. + metadataBackend.incMapviewCount(user, mapConfig.obj().stat_tag, (err) => { + req.profiler.done('incMapviewCount'); + + if (err) { + global.logger.log(`ERROR: failed to increment mapview count for user '${user}': ${err.message}`); + } + + next(); + }); + }; +} + +function augmentLayergroupData () { + return function augmentLayergroupDataMiddleware (req, res, next) { + const layergroup = res.body; + + // include in layergroup response the variables in serverMedata + // those variables are useful to send to the client information + // about how to reach this server or information about it + _.extend(layergroup, global.environment.serverMetadata); + + next(); + }; +} + +function setLastUpdatedTimeToLayergroup () { + return function setLastUpdatedTimeToLayergroupMiddleware (req, res, next) { + const { mapConfigProvider, analysesResults } = res.locals; + const layergroup = res.body; + + mapConfigProvider.getAffectedTables((err, affectedTables) => { + if (err) { + return next(err); + } + + if (!affectedTables) { + return next(); + } + + var lastUpdateTime = affectedTables.getLastUpdatedAt(); + + lastUpdateTime = getLastUpdatedTime(analysesResults, lastUpdateTime) || lastUpdateTime; + + // last update for layergroup cache buster + layergroup.layergroupid = layergroup.layergroupid + ':' + lastUpdateTime; + layergroup.last_updated = new Date(lastUpdateTime).toISOString(); + + next(); + }); + }; +} + +function getLastUpdatedTime(analysesResults, lastUpdateTime) { + if (!Array.isArray(analysesResults)) { + return lastUpdateTime; + } + return analysesResults.reduce(function(lastUpdateTime, analysis) { + return analysis.getNodes().reduce(function(lastNodeUpdatedAtTime, node) { + var nodeUpdatedAtDate = node.getUpdatedAt(); + var nodeUpdatedTimeAt = (nodeUpdatedAtDate && nodeUpdatedAtDate.getTime()) || 0; + return nodeUpdatedTimeAt > lastNodeUpdatedAtTime ? nodeUpdatedTimeAt : lastNodeUpdatedAtTime; + }, lastUpdateTime); + }, lastUpdateTime); +} + +function setLayerStats (pgConnection, statsBackend) { + return function setLayerStatsMiddleware(req, res, next) { + const { user, mapConfig } = res.locals; + const layergroup = res.body; + + pgConnection.getConnection(user, (err, connection) => { + if (err) { + return next(err); + } + + statsBackend.getStats(mapConfig, connection, function(err, layersStats) { + if (err) { + return next(err); + } + + if (layersStats.length > 0) { + layergroup.metadata.layers.forEach(function (layer, index) { + layer.meta.stats = layersStats[index]; + }); + } + + next(); + }); + }); + }; +} + +function setLayergroupIdHeader (templateMaps, useTemplateHash) { + return function setLayergroupIdHeaderMiddleware (req, res, next) { + const { user, template } = res.locals; + const layergroup = res.body; + + if (useTemplateHash) { + var templateHash = templateMaps.fingerPrint(template).substring(0, 8); + layergroup.layergroupid = `${user}@${templateHash}@${layergroup.layergroupid}`; + } + + res.set('X-Layergroup-Id', layergroup.layergroupid); + + next(); + }; +} + +function setDataviewsAndWidgetsUrlsToLayergroupMetadata (layergroupMetadata) { + return function setDataviewsAndWidgetsUrlsToLayergroupMetadataMiddleware (req, res, next) { + const { user, mapConfig } = res.locals; + const layergroup = res.body; + + layergroupMetadata.addDataviewsAndWidgetsUrls(user, layergroup, mapConfig.obj()); + + next(); + }; +} + +function setAnalysesMetadataToLayergroup (layergroupMetadata, includeQuery) { + return function setAnalysesMetadataToLayergroupMiddleware (req, res, next) { + const { user, analysesResults = [] } = res.locals; + const layergroup = res.body; + + layergroupMetadata.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery); + + next(); + }; +} + +function setTurboCartoMetadataToLayergroup (layergroupMetadata) { + return function setTurboCartoMetadataToLayergroupMiddleware (req, res, next) { + const { mapConfig, context } = res.locals; + const layergroup = res.body; + + layergroupMetadata.addTurboCartoContextMetadata(layergroup, mapConfig.obj(), context); + + next(); + }; +} + +function setAggregationMetadataToLayergroup (layergroupMetadata) { + return function setAggregationMetadataToLayergroupMiddleware (req, res, next) { + const { mapConfig, context } = res.locals; + const layergroup = res.body; + + layergroupMetadata.addAggregationContextMetadata(layergroup, mapConfig.obj(), context); + + next(); + }; +} + +function setTilejsonMetadataToLayergroup (layergroupMetadata) { + return function augmentLayergroupTilejsonMiddleware (req, res, next) { + const { user, mapConfig } = res.locals; + const layergroup = res.body; + + layergroupMetadata.addTileJsonMetadata(layergroup, user, mapConfig); + + next(); + }; +} + +function augmentError (options) { + const { addContext = false, label = 'MAPS CONTROLLER' } = options; + + return function augmentErrorMiddleware (err, req, res, next) { + req.profiler.done('error'); + const { mapConfig } = res.locals; + + if (addContext) { + err = Number.isFinite(err.layerIndex) ? populateError(err, mapConfig) : err; + } + + err.label = label; + + next(err); + }; +} + +function populateError(err, mapConfig) { + var error = new Error(err.message); + error.http_status = err.http_status; + + if (!err.http_status && err.message.indexOf('column "the_geom_webmercator" does not exist') >= 0) { + error.http_status = 400; + } + + error.type = 'layer'; + error.subtype = err.message.indexOf('Postgis Plugin') >= 0 ? 'query' : undefined; + error.layer = { + id: mapConfig.getLayerId(err.layerIndex), + index: err.layerIndex, + type: mapConfig.layerType(err.layerIndex) + }; + + return error; +} diff --git a/test/unit/cartodb/ported/windshaft_server.test.js b/test/unit/cartodb/ported/windshaft_server.test.js index e6f07ea7..3f71ff76 100644 --- a/test/unit/cartodb/ported/windshaft_server.test.js +++ b/test/unit/cartodb/ported/windshaft_server.test.js @@ -33,12 +33,4 @@ describe('windshaft', function() { }, /Cannot read property 'mapnik' of undefined/ ); }); - - it('options are set on main windshaft object', function(){ - var ws = cartodbServer(serverOptions); - assert.ok(_.isObject(ws.bind)); - assert.ok(_.isObject(ws.grainstore)); - assert.equal(ws.base_url, '/tiles/:table'); - }); - });