diff --git a/lib/cartodb/controllers/map.js b/lib/cartodb/controllers/map.js deleted file mode 100644 index 9fd67710..00000000 --- a/lib/cartodb/controllers/map.js +++ /dev/null @@ -1,592 +0,0 @@ -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 user = require('../middleware/user'); -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 MapController ( - 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 = MapController; - -MapController.prototype.register = function(app) { - const { base_url_mapconfig: mapConfigBasePath, base_url_templated: templateBasePath } = app; - - app.get( - `${mapConfigBasePath}`, - this.composeCreateMapMiddleware(RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS) - ); - - app.post( - `${mapConfigBasePath}`, - this.composeCreateMapMiddleware(RATE_LIMIT_ENDPOINTS_GROUPS.ANONYMOUS) - ); - - const useTemplate = true; - - app.get( - `${templateBasePath}/:template_id/jsonp`, - this.composeCreateMapMiddleware(RATE_LIMIT_ENDPOINTS_GROUPS.NAMED, useTemplate) - ); - - app.post( - `${templateBasePath}/:template_id`, - this.composeCreateMapMiddleware(RATE_LIMIT_ENDPOINTS_GROUPS.NAMED, useTemplate) - ); - - app.options(`${mapConfigBasePath}`, cors('Content-Type')); -}; - -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; - - return [ - cors(), - user(), - credentials(), - authorize(this.authApi), - dbConnSetup(this.pgConnection), - rateLimit(this.userLimitsApi, endpointGroup), - cleanUpQueryParams(['aggregation']), - initProfiler(isTemplateInstantiation), - checkJsonContentType(), - this.getCreateMapMiddlewares(useTemplate), - 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 }) - ]; -}; - -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'; - - 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 checkCreateLayergroup () { - return function checkCreateLayergroupMiddleware (req, res, next) { - if (req.method === 'GET') { - const { config } = req.query; - - if (!config) { - return next(new Error('layergroup GET needs a "config" parameter')); - } - - try { - req.body = JSON.parse(config); - } catch (err) { - return next(err); - } - } - - req.profiler.done('checkCreateLayergroup'); - 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 = Object.assign({ dbuser, dbname, dbpassword, dbhost, dbport }, req.query); - - 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; - - const { user, api_key } = res.locals; - const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals; - const params = Object.assign({ dbuser, dbname, dbpassword, dbhost, dbport }, req.query); - - const context = { - analysisConfiguration: { - user, - db: { - host: dbhost, - port: dbport, - dbname: dbname, - user: dbuser, - pass: dbpassword - }, - batch: { - username: user, - apiKey: api_key - } - } - }; - - mapConfigAdapter.getMapConfig(user, requestMapConfig, params, context, (err, requestMapConfig) => { - req.profiler.done('anonymous.getMapConfig'); - if (err) { - return next(err); - } - - req.body = requestMapConfig; - res.locals.context = context; - - next(); - }); - }; -} - -function createLayergroup (mapBackend, userLimitsApi, pgConnection, affectedTablesCache) { - return function createLayergroupMiddleware (req, res, next) { - const requestMapConfig = req.body; - - const { context } = res.locals; - const { user, cache_buster, api_key } = res.locals; - const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals; - - const params = { - cache_buster, api_key, - dbuser, dbname, dbpassword, dbhost, dbport - }; - - const datasource = context.datasource || Datasource.EmptyDatasource(); - const mapConfig = new MapConfig(requestMapConfig, datasource); - - const mapConfigProvider = new CreateLayergroupMapConfigProvider( - mapConfig, - user, - userLimitsApi, - pgConnection, - affectedTablesCache, - params - ); - - res.locals.mapConfig = mapConfig; - res.locals.analysesResults = context.analysesResults; - - const mapParams = { dbuser, dbname, dbpassword, dbhost, dbport }; - - mapBackend.createLayergroup(mapConfig, mapParams, mapConfigProvider, (err, layergroup) => { - req.profiler.done('createLayergroup'); - if (err) { - return next(err); - } - - res.body = layergroup; - 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; -}