Merge branch 'master' into metadata

This commit is contained in:
Javier Goizueta 2018-05-11 18:58:38 +02:00
commit 24b1b53ba0
92 changed files with 2375 additions and 1901 deletions

View File

@ -1,14 +1,81 @@
sudo: required
dist: trusty
services:
jobs:
include:
- sudo: required
services:
- docker
language: generic
before_install: docker pull carto/nodejs6-xenial-pg101
script: npm run docker-test
- dist: precise
addons:
postgresql: "9.5"
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- pkg-config
- libcairo2-dev
- libjpeg8-dev
- libgif-dev
- libpango1.0-dev
- g++-4.9
- wget
before_install:
- docker pull cartoimages/windshaft-testing
before_install:
# Add custom PPAs from cartodb
- sudo add-apt-repository -y ppa:cartodb/postgresql-9.5
- sudo add-apt-repository -y ppa:cartodb/gis
- sudo add-apt-repository -y ppa:cartodb/gis-testing
script:
- docker run -e POSTGIS_VERSION=2.4 -v `pwd`:/srv cartoimages/windshaft-testing bash docker-test.sh
- sudo apt-get update
language: generic
# Force instalation of libgeos-3.5.0 (presumably needed because of existing version of postgis)
- sudo apt-get -y install libgeos-3.5.0=3.5.0-1cdb2
# Install postgres db and build deps
- sudo /etc/init.d/postgresql stop # stop travis default instance
- sudo apt-get -y remove --purge postgresql-9.1
- sudo apt-get -y remove --purge postgresql-9.2
- sudo apt-get -y remove --purge postgresql-9.3
- sudo apt-get -y remove --purge postgresql-9.4
- sudo apt-get -y remove --purge postgresql-9.5
- sudo apt-get -y remove --purge postgresql-9.6
- sudo rm -rf /var/lib/postgresql/
- sudo rm -rf /var/log/postgresql/
- sudo rm -rf /etc/postgresql/
- sudo apt-get -y remove --purge postgis-2.2
- sudo apt-get -y autoremove
- sudo apt-get -y install postgresql-9.5=9.5.2-3cdb3
- sudo apt-get -y install postgresql-server-dev-9.5=9.5.2-3cdb3
- sudo apt-get -y install postgresql-plpython-9.5=9.5.2-3cdb3
- sudo apt-get -y install postgresql-9.5-postgis-scripts=2.2.2.0-cdb2
- sudo apt-get -y install postgresql-9.5-postgis-2.2=2.2.2.0-cdb2
# configure it to accept local connections from postgres
- echo -e "# TYPE DATABASE USER ADDRESS METHOD \nlocal all postgres trust\nlocal all all trust\nhost all all 127.0.0.1/32 trust" \
| sudo tee /etc/postgresql/9.5/main/pg_hba.conf
- sudo /etc/init.d/postgresql restart 9.5
- createdb template_postgis
- createuser publicuser
- psql -c "CREATE EXTENSION postgis" template_postgis
# install yarn 0.27.5
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 0.27.5
- export PATH="$HOME/.yarn/bin:$PATH"
# instal redis 4
- wget http://download.redis.io/releases/redis-4.0.8.tar.gz
- tar xvzf redis-4.0.8.tar.gz
- cd redis-4.0.8
- make
- sudo make install
- cd ..
- rm redis-4.0.8.tar.gz
env:
- NPROCS=1 JOBS=1 PGUSER=postgres CXX=g++-4.9
language: node_js
node_js:
- "6"

View File

@ -4,6 +4,7 @@
Released 2018-mm-dd
New features:
- CI tests with Ubuntu Xenial + PostgreSQL 10.1 and Ubuntu Precise + PostgreSQL 9.5
Bug Fixes:

2
app.js
View File

@ -100,8 +100,6 @@ if ( global.environment.log_filename ) {
global.log4js.configure(log4jsConfig);
global.logger = global.log4js.getLogger();
global.environment.api_hostname = require('os').hostname().split('.')[0];
// Include cartodb_windshaft only _after_ the "global" variable is set
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/28
var cartodbWindshaft = require('./lib/cartodb/server');

View File

@ -13,6 +13,8 @@ var config = {
// from hostname. Must have a single grabbing block.
,user_from_host: '^(.*)\\.localhost'
// DEPRECATED: use routes property instead
// ---------------------------------------
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
@ -26,6 +28,52 @@ var config = {
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Base URLs for the APIs
//
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
,routes: {
v1: {
paths: [
'/api/v1',
'/user/:user/api/v1',
],
// Base url for the Detached Maps API
// "/api/v1/map" is the new API,
map: {
paths: [
'/map',
]
},
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
template: {
paths: [
'/map/named'
]
}
},
// For compatibility with versions up to 1.6.x
v0: {
paths: [
'/tiles'
],
// Base url for the Detached Maps API
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
map: {
paths: [
'/layergroup'
]
},
// Base url for the Templated Maps API
// "/tiles/template" is for compatibility with versions up to 1.6.x
template: {
paths: [
'/template'
]
}
}
}
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
//
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be

View File

@ -13,6 +13,8 @@ var config = {
// from hostname. Must have a single grabbing block.
,user_from_host: '^(.*)\\.cartodb\\.com$'
// DEPRECATED: use routes property instead
// ---------------------------------------
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
@ -26,6 +28,52 @@ var config = {
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Base URLs for the APIs
//
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
,routes: {
v1: {
paths: [
'/api/v1',
'/user/:user/api/v1',
],
// Base url for the Detached Maps API
// "/api/v1/map" is the new API,
map: {
paths: [
'/map',
]
},
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
template: {
paths: [
'/map/named'
]
}
},
// For compatibility with versions up to 1.6.x
v0: {
paths: [
'/tiles'
],
// Base url for the Detached Maps API
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
map: {
paths: [
'/layergroup'
]
},
// Base url for the Templated Maps API
// "/tiles/template" is for compatibility with versions up to 1.6.x
template: {
paths: [
'/template'
]
}
}
}
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
//
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be

View File

@ -13,6 +13,8 @@ var config = {
// from hostname. Must have a single grabbing block.
,user_from_host: '^(.*)\\.cartodb\\.com$'
// DEPRECATED: use routes property instead
// ---------------------------------------
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
@ -26,6 +28,52 @@ var config = {
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Base URLs for the APIs
//
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
,routes: {
v1: {
paths: [
'/api/v1',
'/user/:user/api/v1',
],
// Base url for the Detached Maps API
// "/api/v1/map" is the new API,
map: {
paths: [
'/map',
]
},
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
template: {
paths: [
'/map/named'
]
}
},
// For compatibility with versions up to 1.6.x
v0: {
paths: [
'/tiles'
],
// Base url for the Detached Maps API
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
map: {
paths: [
'/layergroup'
]
},
// Base url for the Templated Maps API
// "/tiles/template" is for compatibility with versions up to 1.6.x
template: {
paths: [
'/template'
]
}
}
}
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
//
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be

View File

@ -13,6 +13,8 @@ var config = {
// from hostname. Must have a single grabbing block.
,user_from_host: '(.*)'
// DEPRECATED: use routes property instead
// ---------------------------------------
// Base URLs for the APIs
//
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
@ -26,6 +28,52 @@ var config = {
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Base URLs for the APIs
//
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
,routes: {
v1: {
paths: [
'/api/v1',
'/user/:user/api/v1',
],
// Base url for the Detached Maps API
// "/api/v1/map" is the new API,
map: {
paths: [
'/map',
]
},
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
template: {
paths: [
'/map/named'
]
}
},
// For compatibility with versions up to 1.6.x
v0: {
paths: [
'/tiles'
],
// Base url for the Detached Maps API
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
map: {
paths: [
'/layergroup'
]
},
// Base url for the Templated Maps API
// "/tiles/template" is for compatibility with versions up to 1.6.x
template: {
paths: [
'/template'
]
}
}
}
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
//
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be

View File

@ -1,11 +0,0 @@
export NPROCS=1 && export JOBS=1 && export CXX=g++-4.9 && export PGUSER=postgres
npm install -g yarn@0.27.5
yarn
/etc/init.d/postgresql start
createdb template_postgis && createuser publicuser
psql -c "CREATE EXTENSION postgis" template_postgis
POSTGIS_VERSION=2.4 npm test

View File

@ -0,0 +1,89 @@
FROM ubuntu:xenial
# Use UTF8 to avoid encoding problems with pgsql
ENV LANG C.UTF-8
ENV NPROCS 1
ENV JOBS 1
ENV CXX g++-4.9
ENV PGUSER postgres
# Add external repos
RUN set -ex \
&& apt-get update \
&& apt-get install -y \
curl \
software-properties-common \
locales \
&& add-apt-repository -y ppa:ubuntu-toolchain-r/test \
&& add-apt-repository -y ppa:cartodb/postgresql-10 \
&& add-apt-repository -y ppa:cartodb/gis \
&& curl -sL https://deb.nodesource.com/setup_6.x | bash \
&& locale-gen en_US.UTF-8 \
&& update-locale LANG=en_US.UTF-8
# Install dependencies and PostGIS 2.4 from sources
RUN set -ex \
&& apt-get update \
&& apt-get install -y \
g++-4.9 \
gcc-4.9 \
git \
libcairo2-dev \
libgdal-dev \
libgdal1i \
libgdal20 \
libgeos-dev \
libgif-dev \
libjpeg8-dev \
libjson-c-dev \
libpango1.0-dev \
libpixman-1-dev \
libproj-dev \
libprotobuf-c-dev \
libxml2-dev \
gdal-bin \
make \
nodejs \
protobuf-c-compiler \
pkg-config \
wget \
zip \
postgresql-10 \
postgresql-10-plproxy \
postgresql-10-postgis-2.4 \
postgresql-10-postgis-2.4-scripts \
postgresql-10-postgis-scripts \
postgresql-client-10 \
postgresql-client-common \
postgresql-common \
postgresql-contrib \
postgresql-plpython-10 \
postgresql-server-dev-10 \
postgis \
&& wget http://download.redis.io/releases/redis-4.0.8.tar.gz \
&& tar xvzf redis-4.0.8.tar.gz \
&& cd redis-4.0.8 \
&& make \
&& make install \
&& cd .. \
&& rm redis-4.0.8.tar.gz \
&& rm -R redis-4.0.8 \
&& apt-get purge -y wget protobuf-c-compiler \
&& apt-get autoremove -y
# Configure PostgreSQL
RUN set -ex \
&& echo "listen_addresses='*'" >> /etc/postgresql/10/main/postgresql.conf \
&& echo "local all all trust" > /etc/postgresql/10/main/pg_hba.conf \
&& echo "host all all 0.0.0.0/0 trust" >> /etc/postgresql/10/main/pg_hba.conf \
&& echo "host all all ::1/128 trust" >> /etc/postgresql/10/main/pg_hba.conf \
&& /etc/init.d/postgresql start \
&& createdb template_postgis \
&& createuser publicuser \
&& psql -c "CREATE EXTENSION postgis" template_postgis \
&& /etc/init.d/postgresql stop
WORKDIR /srv
EXPOSE 5858
CMD /etc/init.d/postgresql start

23
docker/reference.md Normal file
View File

@ -0,0 +1,23 @@
After running the tests with docker, you will need Docker installed and the docker image downloaded.
## Install docker
`sudo apt install docker.io && sudo usermod -aG docker $(whoami)`
## Download image
`docker pull carto/IMAGE`
## Carto account
https://hub.docker.com/r/carto/
## Update image
- Edit the docker image file with your desired changes
- Build image:
- `docker build -t carto/IMAGE -f docker/DOCKER_FILE docker/`
- Upload to docker hub:
- Login into docker hub:
- `docker login`
- Create tag:
- `docker tag carto/IMAGE carto/IMAGE`
- Upload:
- `docker push carto/IMAGE`

View File

@ -0,0 +1,319 @@
const { Router: router } = require('express');
const RedisPool = require('redis-mpool');
const cartodbRedis = require('cartodb-redis');
const windshaft = require('windshaft');
const PgConnection = require('../backends/pg_connection');
const AnalysisBackend = require('../backends/analysis');
const AnalysisStatusBackend = require('../backends/analysis-status');
const DataviewBackend = require('../backends/dataview');
const TemplateMaps = require('../backends/template_maps.js');
const PgQueryRunner = require('../backends/pg_query_runner');
const StatsBackend = require('../backends/stats');
const AuthBackend = require('../backends/auth');
const UserLimitsBackend = require('../backends/user-limits');
const OverviewsMetadataBackend = require('../backends/overviews-metadata');
const FilterStatsApi = require('../backends/filter-stats');
const TablesExtentBackend = require('../backends/tables-extent');
const LayergroupAffectedTablesCache = require('../cache/layergroup_affected_tables');
const SurrogateKeysCache = require('../cache/surrogate_keys_cache');
const VarnishHttpCacheBackend = require('../cache/backend/varnish_http');
const FastlyCacheBackend = require('../cache/backend/fastly');
const NamedMapProviderCache = require('../cache/named_map_provider_cache');
const NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
const SqlWrapMapConfigAdapter = require('../models/mapconfig/adapter/sql-wrap-mapconfig-adapter');
const MapConfigNamedLayersAdapter = require('../models/mapconfig/adapter/mapconfig-named-layers-adapter');
const MapConfigBufferSizeAdapter = require('../models/mapconfig/adapter/mapconfig-buffer-size-adapter');
const AnalysisMapConfigAdapter = require('../models/mapconfig/adapter/analysis-mapconfig-adapter');
const MapConfigOverviewsAdapter = require('../models/mapconfig/adapter/mapconfig-overviews-adapter');
const TurboCartoAdapter = require('../models/mapconfig/adapter/turbo-carto-adapter');
const DataviewsWidgetsAdapter = require('../models/mapconfig/adapter/dataviews-widgets-adapter');
const AggregationMapConfigAdapter = require('../models/mapconfig/adapter/aggregation-mapconfig-adapter');
const MapConfigAdapter = require('../models/mapconfig/adapter');
const ResourceLocator = require('../models/resource-locator');
const LayergroupMetadata = require('../utils/layergroup-metadata');
const RendererStatsReporter = require('../stats/reporter/renderer');
const initializeStatusCode = require('./middlewares/initialize-status-code');
const logger = require('./middlewares/logger');
const bodyParser = require('body-parser');
const servedByHostHeader = require('./middlewares/served-by-host-header');
const stats = require('./middlewares/stats');
const lzmaMiddleware = require('./middlewares/lzma');
const cors = require('./middlewares/cors');
const user = require('./middlewares/user');
const sendResponse = require('./middlewares/send-response');
const syntaxError = require('./middlewares/syntax-error');
const errorMiddleware = require('./middlewares/error-middleware');
const MapRouter = require('./map/map-router');
const TemplateRouter = require('./template/template-router');
module.exports = class ApiRouter {
constructor ({ serverOptions, environmentOptions }) {
this.serverOptions = serverOptions;
const redisOptions = Object.assign({
name: 'windshaft-server',
unwatchOnRelease: false,
noReadyCheck: true
}, environmentOptions.redis);
const redisPool = new RedisPool(redisOptions);
redisPool.on('status', function (status) {
var keyPrefix = 'windshaft.redis-pool.' + status.name + '.db' + status.db + '.';
global.statsClient.gauge(keyPrefix + 'count', status.count);
global.statsClient.gauge(keyPrefix + 'unused', status.unused);
global.statsClient.gauge(keyPrefix + 'waiting', status.waiting);
});
const metadataBackend = cartodbRedis({ pool: redisPool });
const pgConnection = new PgConnection(metadataBackend);
const mapStore = new windshaft.storage.MapStore({
pool: redisPool,
expire_time: serverOptions.grainstore.default_layergroup_ttl
});
const rendererFactory = createRendererFactory({ redisPool, serverOptions, environmentOptions });
const rendererCacheOpts = Object.assign({
ttl: 60000, // 60 seconds TTL by default
statsInterval: 60000 // reports stats every milliseconds defined here
}, serverOptions.renderCache || {});
const rendererCache = new windshaft.cache.RendererCache(rendererFactory, rendererCacheOpts);
const rendererStatsReporter = new RendererStatsReporter(rendererCache, rendererCacheOpts.statsInterval);
rendererStatsReporter.start();
const tileBackend = new windshaft.backend.Tile(rendererCache);
const attributesBackend = new windshaft.backend.Attributes();
const previewBackend = new windshaft.backend.Preview(rendererCache);
const mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
const mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);
const surrogateKeysCacheBackends = createSurrogateKeysCacheBackends(serverOptions);
const surrogateKeysCache = new SurrogateKeysCache(surrogateKeysCacheBackends);
const templateMaps = createTemplateMaps({ redisPool, surrogateKeysCache });
const analysisStatusBackend = new AnalysisStatusBackend();
const analysisBackend = new AnalysisBackend(metadataBackend, serverOptions.analysis);
const dataviewBackend = new DataviewBackend(analysisBackend);
const statsBackend = new StatsBackend();
const userLimitsBackend = new UserLimitsBackend(metadataBackend, {
limits: {
cacheOnTimeout: serverOptions.renderer.mapnik.limits.cacheOnTimeout || false,
render: serverOptions.renderer.mapnik.limits.render || 0,
rateLimitsEnabled: global.environment.enabledFeatures.rateLimitsEnabled
}
});
const authBackend = new AuthBackend(pgConnection, metadataBackend, mapStore, templateMaps);
const layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
if (process.env.NODE_ENV === 'test') {
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
}
const pgQueryRunner = new PgQueryRunner(pgConnection);
const overviewsMetadataBackend = new OverviewsMetadataBackend(pgQueryRunner);
const filterStatsBackend = new FilterStatsApi(pgQueryRunner);
const tablesExtentBackend = new TablesExtentBackend(pgQueryRunner);
const mapConfigAdapter = new MapConfigAdapter(
new MapConfigNamedLayersAdapter(templateMaps, pgConnection),
new MapConfigBufferSizeAdapter(),
new SqlWrapMapConfigAdapter(),
new DataviewsWidgetsAdapter(),
new AnalysisMapConfigAdapter(analysisBackend),
new AggregationMapConfigAdapter(pgConnection),
new MapConfigOverviewsAdapter(overviewsMetadataBackend, filterStatsBackend),
new TurboCartoAdapter()
);
const resourceLocator = new ResourceLocator(global.environment);
const layergroupMetadata = new LayergroupMetadata(resourceLocator);
const namedMapProviderCache = new NamedMapProviderCache(
templateMaps,
pgConnection,
metadataBackend,
userLimitsBackend,
mapConfigAdapter,
layergroupAffectedTablesCache
);
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, namedMapProviderCache.invalidate.bind(namedMapProviderCache));
});
const collaborators = {
analysisStatusBackend,
attributesBackend,
dataviewBackend,
previewBackend,
tileBackend,
pgConnection,
mapStore,
userLimitsBackend,
layergroupAffectedTablesCache,
authBackend,
surrogateKeysCache,
templateMaps,
mapBackend,
metadataBackend,
mapConfigAdapter,
statsBackend,
layergroupMetadata,
namedMapProviderCache,
tablesExtentBackend
};
this.mapRouter = new MapRouter({ collaborators });
this.templateRouter = new TemplateRouter({ collaborators });
}
register (app) {
// FIXME: we need a better way to reset cache while running tests
if (process.env.NODE_ENV === 'test') {
app.layergroupAffectedTablesCache = this.layergroupAffectedTablesCache;
}
Object.keys(this.serverOptions.routes).forEach(apiVersion => {
const routes = this.serverOptions.routes[apiVersion];
const apiRouter = router();
apiRouter.use(logger(this.serverOptions));
apiRouter.use(initializeStatusCode());
apiRouter.use(bodyParser.json());
apiRouter.use(servedByHostHeader());
apiRouter.use(stats({
enabled: this.serverOptions.useProfiler,
statsClient: global.statsClient
}));
apiRouter.use(lzmaMiddleware());
apiRouter.use(cors());
apiRouter.use(user());
this.templateRouter.register(apiRouter, routes.template.paths);
this.mapRouter.register(apiRouter, routes.map.paths);
apiRouter.use(sendResponse());
apiRouter.use(syntaxError());
apiRouter.use(errorMiddleware());
const apiPaths = routes.paths;
apiPaths.forEach(path => app.use(path, apiRouter));
});
}
};
function createTemplateMaps ({ redisPool, surrogateKeysCache }) {
const templateMaps = new TemplateMaps(redisPool, {
max_user_templates: global.environment.maxUserTemplates
});
function invalidateNamedMap (owner, templateName) {
var startTime = Date.now();
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
var logMessage = JSON.stringify({
username: owner,
type: 'named_map_invalidation',
elapsed: Date.now() - startTime,
error: !!err ? JSON.stringify(err.message) : undefined
});
if (err) {
global.logger.warn(logMessage);
} else {
global.logger.info(logMessage);
}
});
}
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, invalidateNamedMap);
});
return templateMaps;
}
function createSurrogateKeysCacheBackends(serverOptions) {
var cacheBackends = [];
if (serverOptions.varnish_purge_enabled) {
cacheBackends.push(
new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port)
);
}
if (serverOptions.fastly &&
!!serverOptions.fastly.enabled && !!serverOptions.fastly.apiKey && !!serverOptions.fastly.serviceId) {
cacheBackends.push(
new FastlyCacheBackend(serverOptions.fastly.apiKey, serverOptions.fastly.serviceId)
);
}
return cacheBackends;
}
const timeoutErrorTilePath = __dirname + '/../../../assets/render-timeout-fallback.png';
const timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
function createRendererFactory ({ redisPool, serverOptions, environmentOptions }) {
var onTileErrorStrategy;
if (environmentOptions.enabledFeatures.onTileErrorStrategy !== false) {
onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(err, tile, headers, stats, format, callback) {
function isRenderTimeoutError (err) {
return err.message === 'Render timed out';
}
function isDatasourceTimeoutError (err) {
return err.message && err.message.match(/canceling statement due to statement timeout/i);
}
function isTimeoutError (err) {
return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
}
function isRasterFormat (format) {
return format === 'png' || format === 'jpg';
}
if (isTimeoutError(err) && isRasterFormat(format)) {
return callback(null, timeoutErrorTile, {
'Content-Type': 'image/png',
}, {});
} else {
return callback(err, tile, headers, stats);
}
};
}
const rendererFactory = new windshaft.renderer.Factory({
onTileErrorStrategy: onTileErrorStrategy,
mapnik: {
redisPool: redisPool,
grainstore: serverOptions.grainstore,
mapnik: serverOptions.renderer.mapnik
},
http: serverOptions.renderer.http,
mvt: serverOptions.renderer.mvt
});
return rendererFactory;
}

View File

@ -1,42 +1,34 @@
const PSQL = require('cartodb-psql');
const cors = require('../middleware/cors');
const user = require('../middleware/user');
const cleanUpQueryParams = require('../middleware/clean-up-query-params');
const credentials = require('../middleware/credentials');
const authorize = require('../middleware/authorize');
const dbConnSetup = require('../middleware/db-conn-setup');
const rateLimit = require('../middleware/rate-limit');
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const authorize = require('../middlewares/authorize');
const dbConnSetup = require('../middlewares/db-conn-setup');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const cacheControlHeader = require('../middleware/cache-control-header');
const sendResponse = require('../middleware/send-response');
const dbParamsFromResLocals = require('../utils/database-params');
const cacheControlHeader = require('../middlewares/cache-control-header');
const dbParamsFromResLocals = require('../../utils/database-params');
function AnalysesController(pgConnection, authApi, userLimitsApi) {
function AnalysesController(pgConnection, authBackend, userLimitsBackend) {
this.pgConnection = pgConnection;
this.authApi = authApi;
this.userLimitsApi = userLimitsApi;
this.authBackend = authBackend;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = AnalysesController;
AnalysesController.prototype.register = function (app) {
const { base_url_mapconfig: mapconfigBasePath } = app;
app.get(
`${mapconfigBasePath}/analyses/catalog`,
cors(),
user(),
AnalysesController.prototype.register = function (mapRouter) {
mapRouter.get(
`/analyses/catalog`,
credentials(),
authorize(this.authApi),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.ANALYSIS_CATALOG),
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 }),
sendResponse(),
unauthorizedError()
);
};
@ -107,6 +99,7 @@ function prepareResponse () {
return -1;
});
res.statusCode = 200;
res.body = { catalog: analysisCatalog };
next();

View File

@ -0,0 +1,57 @@
const layergroupToken = require('../middlewares/layergroup-token');
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const dbParamsFromResLocals = require('../../utils/database-params');
module.exports = class AnalysisLayergroupController {
constructor (analysisStatusBackend, pgConnection, userLimitsBackend, authBackend) {
this.analysisStatusBackend = analysisStatusBackend;
this.pgConnection = pgConnection;
this.userLimitsBackend = userLimitsBackend;
this.authBackend = authBackend;
}
register (mapRouter) {
mapRouter.get(
`/:token/analysis/node/:nodeId`,
layergroupToken(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ANALYSIS),
cleanUpQueryParams(),
analysisNodeStatus(this.analysisStatusBackend)
);
}
};
function analysisNodeStatus (analysisStatusBackend) {
return function analysisNodeStatusMiddleware(req, res, next) {
const { nodeId } = req.params;
const dbParams = dbParamsFromResLocals(res.locals);
analysisStatusBackend.getNodeStatus(nodeId, dbParams, (err, nodeStatus, stats = {}) => {
req.profiler.add(stats);
if (err) {
err.label = 'GET NODE STATUS';
return next(err);
}
res.set({
'Cache-Control': 'public,max-age=5',
'Last-Modified': new Date().toUTCString()
});
res.statusCode = 200;
res.body = nodeStatus;
next();
});
};
}

View File

@ -0,0 +1,212 @@
const windshaft = require('windshaft');
const MapConfig = windshaft.model.MapConfig;
const Datasource = windshaft.model.Datasource;
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const initProfiler = require('../middlewares/init-profiler');
const checkJsonContentType = require('../middlewares/check-json-content-type');
const incrementMapViewCount = require('../middlewares/increment-map-view-count');
const augmentLayergroupData = require('../middlewares/augment-layergroup-data');
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 lastUpdatedTimeLayergroup = require('../middlewares/last-updated-time-layergroup');
const layerStats = require('../middlewares/layer-stats');
const layergroupIdHeader = require('../middlewares/layergroup-id-header');
const layergroupMetadata = require('../middlewares/layergroup-metadata');
const mapError = require('../middlewares/map-error');
const CreateLayergroupMapConfigProvider = require('../../models/mapconfig/provider/create-layergroup-provider');
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 = AnonymousMapController;
AnonymousMapController.prototype.register = function (mapRouter) {
mapRouter.options('/');
mapRouter.get('/', this.composeCreateMapMiddleware());
mapRouter.post('/', this.composeCreateMapMiddleware());
};
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 })
];
};
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 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, userLimitsBackend, 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,
userLimitsBackend,
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.statusCode = 200;
res.body = layergroup;
res.locals.mapConfigProvider = mapConfigProvider;
next();
});
};
}

View File

@ -1,54 +1,47 @@
const cors = require('../../middleware/cors');
const user = require('../../middleware/user');
const layergroupToken = require('../../middleware/layergroup-token');
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 rateLimit = require('../../middleware/rate-limit');
const layergroupToken = require('../middlewares/layergroup-token');
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const createMapStoreMapConfigProvider = require('./middlewares/map-store-map-config-provider');
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 createMapStoreMapConfigProvider = require('../middlewares/map-store-map-config-provider');
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');
module.exports = class AttribitesController {
module.exports = class AttributesLayergroupController {
constructor (
attributesBackend,
pgConnection,
mapStore,
userLimitsApi,
userLimitsBackend,
layergroupAffectedTablesCache,
authApi,
authBackend,
surrogateKeysCache
) {
this.attributesBackend = attributesBackend;
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.userLimitsApi = userLimitsApi;
this.userLimitsBackend = userLimitsBackend;
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
this.authApi = authApi;
this.authBackend = authBackend;
this.surrogateKeysCache = surrogateKeysCache;
}
register (app) {
const { base_url_mapconfig: mapConfigBasePath } = app;
app.get(
`${mapConfigBasePath}/:token/:layer/attributes/:fid`,
cors(),
user(),
register (mapRouter) {
mapRouter.get(
`/:token/:layer/attributes/:fid`,
layergroupToken(),
credentials(),
authorize(this.authApi),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.ATTRIBUTES),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.ATTRIBUTES),
cleanUpQueryParams(),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
@ -56,8 +49,7 @@ module.exports = class AttribitesController {
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
lastModifiedHeader()
);
}
};
@ -85,6 +77,7 @@ function getFeatureAttributes (attributesBackend) {
return next(err);
}
res.statusCode = 200;
res.body = tile;
next();

View File

@ -1,18 +1,15 @@
const cors = require('../../middleware/cors');
const user = require('../../middleware/user');
const layergroupToken = require('../../middleware/layergroup-token');
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 rateLimit = require('../../middleware/rate-limit');
const layergroupToken = require('../middlewares/layergroup-token');
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const createMapStoreMapConfigProvider = require('./middlewares/map-store-map-config-provider');
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 createMapStoreMapConfigProvider = require('../middlewares/map-store-map-config-provider');
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 ALLOWED_DATAVIEW_QUERY_PARAMS = [
'filters', // json
@ -29,44 +26,40 @@ const ALLOWED_DATAVIEW_QUERY_PARAMS = [
'categories', // number
];
module.exports = class DataviewController {
module.exports = class DataviewLayergroupController {
constructor (
dataviewBackend,
pgConnection,
mapStore,
userLimitsApi,
userLimitsBackend,
layergroupAffectedTablesCache,
authApi,
authBackend,
surrogateKeysCache
) {
this.dataviewBackend = dataviewBackend;
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.userLimitsApi = userLimitsApi;
this.userLimitsBackend = userLimitsBackend;
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
this.authApi = authApi;
this.authBackend = authBackend;
this.surrogateKeysCache = surrogateKeysCache;
}
register (app) {
const { base_url_mapconfig: mapConfigBasePath } = app;
register (mapRouter) {
// Undocumented/non-supported API endpoint methods.
// Use at your own peril.
app.get(
`${mapConfigBasePath}/:token/dataview/:dataviewName`,
cors(),
user(),
mapRouter.get(
`/:token/dataview/:dataviewName`,
layergroupToken(),
credentials(),
authorize(this.authApi),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
@ -74,23 +67,20 @@ module.exports = class DataviewController {
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
lastModifiedHeader()
);
app.get(
`${mapConfigBasePath}/:token/:layer/widget/:dataviewName`,
cors(),
user(),
mapRouter.get(
`/:token/:layer/widget/:dataviewName`,
layergroupToken(),
credentials(),
authorize(this.authApi),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
@ -98,23 +88,20 @@ module.exports = class DataviewController {
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
lastModifiedHeader()
);
app.get(
`${mapConfigBasePath}/:token/dataview/:dataviewName/search`,
cors(),
user(),
mapRouter.get(
`/:token/dataview/:dataviewName/search`,
layergroupToken(),
credentials(),
authorize(this.authApi),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
@ -122,23 +109,20 @@ module.exports = class DataviewController {
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
lastModifiedHeader()
);
app.get(
`${mapConfigBasePath}/:token/:layer/widget/:dataviewName/search`,
cors(),
user(),
mapRouter.get(
`/:token/:layer/widget/:dataviewName/search`,
layergroupToken(),
credentials(),
authorize(this.authApi),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
@ -146,8 +130,7 @@ module.exports = class DataviewController {
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
lastModifiedHeader()
);
}
};
@ -168,6 +151,7 @@ function getDataview (dataviewBackend) {
return next(err);
}
res.statusCode = 200;
res.body = dataview;
next();
@ -191,6 +175,7 @@ function dataviewSearch (dataviewBackend) {
return next(err);
}
res.statusCode = 200;
res.body = searchResult;
next();

View File

@ -0,0 +1,129 @@
const { Router: router } = require('express');
const AnalysisLayergroupController = require('./analysis-layergroup-controller');
const AttributesLayergroupController = require('./attributes-layergroup-controller');
const DataviewLayergroupController = require('./dataview-layergroup-controller');
const PreviewLayergroupController = require('./preview-layergroup-controller');
const TileLayergroupController = require('./tile-layergroup-controller');
const AnonymousMapController = require('./anonymous-map-controller');
const PreviewTemplateController = require('./preview-template-controller');
const AnalysesCatalogController = require('./analyses-catalog-controller');
module.exports = class MapRouter {
constructor ({ collaborators }) {
const {
analysisStatusBackend,
attributesBackend,
dataviewBackend,
previewBackend,
tileBackend,
pgConnection,
mapStore,
userLimitsBackend,
layergroupAffectedTablesCache,
authBackend,
surrogateKeysCache,
templateMaps,
mapBackend,
metadataBackend,
mapConfigAdapter,
statsBackend,
layergroupMetadata,
namedMapProviderCache,
tablesExtentBackend
} = collaborators;
this.analysisLayergroupController = new AnalysisLayergroupController(
analysisStatusBackend,
pgConnection,
userLimitsBackend,
authBackend
);
this.attributesLayergroupController = new AttributesLayergroupController(
attributesBackend,
pgConnection,
mapStore,
userLimitsBackend,
layergroupAffectedTablesCache,
authBackend,
surrogateKeysCache
);
this.dataviewLayergroupController = new DataviewLayergroupController(
dataviewBackend,
pgConnection,
mapStore,
userLimitsBackend,
layergroupAffectedTablesCache,
authBackend,
surrogateKeysCache
);
this.previewLayergroupController = new PreviewLayergroupController(
previewBackend,
pgConnection,
mapStore,
userLimitsBackend,
layergroupAffectedTablesCache,
authBackend,
surrogateKeysCache
);
this.tileLayergroupController = new TileLayergroupController(
tileBackend,
pgConnection,
mapStore,
userLimitsBackend,
layergroupAffectedTablesCache,
authBackend,
surrogateKeysCache
);
this.anonymousMapController = new AnonymousMapController(
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
surrogateKeysCache,
userLimitsBackend,
layergroupAffectedTablesCache,
mapConfigAdapter,
statsBackend,
authBackend,
layergroupMetadata
);
this.previewTemplateController = new PreviewTemplateController(
namedMapProviderCache,
previewBackend,
surrogateKeysCache,
tablesExtentBackend,
metadataBackend,
pgConnection,
authBackend,
userLimitsBackend
);
this.analysesController = new AnalysesCatalogController(
pgConnection,
authBackend,
userLimitsBackend
);
}
register (apiRouter, mapPaths) {
const mapRouter = router();
this.analysisLayergroupController.register(mapRouter);
this.attributesLayergroupController.register(mapRouter);
this.dataviewLayergroupController.register(mapRouter);
this.previewLayergroupController.register(mapRouter);
this.tileLayergroupController.register(mapRouter);
this.anonymousMapController.register(mapRouter);
this.previewTemplateController.register(mapRouter);
this.analysesController.register(mapRouter);
mapPaths.forEach(path => apiRouter.use(path, mapRouter));
}
};

View File

@ -1,58 +1,51 @@
const cors = require('../../middleware/cors');
const user = require('../../middleware/user');
const layergroupToken = require('../../middleware/layergroup-token');
const coordinates = require('../../middleware/coordinates');
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 rateLimit = require('../../middleware/rate-limit');
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 dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const createMapStoreMapConfigProvider = require('./middlewares/map-store-map-config-provider');
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 createMapStoreMapConfigProvider = require('../middlewares/map-store-map-config-provider');
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');
module.exports = class StaticController {
module.exports = class PreviewLayergroupController {
constructor (
previewBackend,
pgConnection,
mapStore,
userLimitsApi,
userLimitsBackend,
layergroupAffectedTablesCache,
authApi,
authBackend,
surrogateKeysCache
) {
this.previewBackend = previewBackend;
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.userLimitsApi = userLimitsApi;
this.userLimitsBackend = userLimitsBackend;
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
this.authApi = authApi;
this.authBackend = authBackend;
this.surrogateKeysCache = surrogateKeysCache;
}
register (app) {
const { base_url_mapconfig: mapConfigBasePath } = app;
register (mapRouter) {
const forcedFormat = 'png';
app.get(
`${mapConfigBasePath}/static/center/:token/:z/:lat/:lng/:width/:height.:format`,
cors(),
user(),
mapRouter.get(
`/static/center/:token/:z/:lat/:lng/:width/:height.:format`,
layergroupToken(),
coordinates({ z: true, x: false, y: false }),
credentials(),
authorize(this.authApi),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC),
cleanUpQueryParams(['layer']),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache,
forcedFormat
@ -61,23 +54,20 @@ module.exports = class StaticController {
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
lastModifiedHeader()
);
app.get(
`${mapConfigBasePath}/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`,
cors(),
user(),
mapRouter.get(
`/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`,
layergroupToken(),
credentials(),
authorize(this.authApi),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC),
cleanUpQueryParams(['layer']),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache,
forcedFormat
@ -86,8 +76,7 @@ module.exports = class StaticController {
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
lastModifiedHeader()
);
}
};
@ -120,6 +109,7 @@ function getPreviewImageByCenter (previewBackend) {
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
res.statusCode = 200;
res.body = image;
next();
@ -155,6 +145,7 @@ function getPreviewImageByBoundingBox (previewBackend) {
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
res.statusCode = 200;
res.body = image;
next();

View File

@ -1,17 +1,13 @@
const cors = require('../middleware/cors');
const user = require('../middleware/user');
const cleanUpQueryParams = require('../middleware/clean-up-query-params');
const coordinates = require('../middleware/coordinates');
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 vectorError = require('../middleware/vector-error');
const rateLimit = require('../middleware/rate-limit');
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const namedMapProvider = require('../middlewares/named-map-provider');
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 rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const DEFAULT_ZOOM_CENTER = {
@ -26,70 +22,37 @@ function numMapper(n) {
return +n;
}
function NamedMapsController (
function PreviewTemplateController (
namedMapProviderCache,
tileBackend,
previewBackend,
surrogateKeysCache,
tablesExtentApi,
tablesExtentBackend,
metadataBackend,
pgConnection,
authApi,
userLimitsApi
authBackend,
userLimitsBackend
) {
this.namedMapProviderCache = namedMapProviderCache;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.tablesExtentApi = tablesExtentApi;
this.tablesExtentBackend = tablesExtentBackend;
this.metadataBackend = metadataBackend;
this.pgConnection = pgConnection;
this.authApi = authApi;
this.userLimitsApi = userLimitsApi;
this.authBackend = authBackend;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = NamedMapsController;
module.exports = PreviewTemplateController;
NamedMapsController.prototype.register = function(app) {
const { base_url_mapconfig: mapconfigBasePath, base_url_templated: templateBasePath } = app;
app.get(
`${templateBasePath}/:template_id/:layer/:z/:x/:y.(:format)`,
cors(),
user(),
coordinates(),
PreviewTemplateController.prototype.register = function (mapRouter) {
mapRouter.get(
`/static/named/:template_id/:width/:height.:format`,
credentials(),
authorize(this.authApi),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_TILES),
cleanUpQueryParams(),
getNamedMapProvider({
namedMapProviderCache: this.namedMapProviderCache,
label: 'NAMED_MAP_TILE'
}),
getTile({
tileBackend: this.tileBackend,
label: 'NAMED_MAP_TILE'
}),
setContentTypeHeader(),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse(),
vectorError()
);
app.get(
`${mapconfigBasePath}/static/named/:template_id/:width/:height.:format`,
cors(),
user(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC_NAMED),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC_NAMED),
cleanUpQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
getNamedMapProvider({
namedMapProvider({
namedMapProviderCache: this.namedMapProviderCache,
label: 'STATIC_VIZ_MAP', forcedFormat: 'png'
}),
@ -98,51 +61,17 @@ NamedMapsController.prototype.register = function(app) {
namedMapProviderCache: this.namedMapProviderCache,
label: 'STATIC_VIZ_MAP'
}),
getStaticImageOptions({ tablesExtentApi: this.tablesExtentApi }),
getStaticImageOptions({ tablesExtentBackend: this.tablesExtentBackend }),
getImage({ previewBackend: this.previewBackend, label: 'STATIC_VIZ_MAP' }),
setContentTypeHeader(),
incrementMapViews({ metadataBackend: this.metadataBackend }),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
sendResponse()
lastModifiedHeader()
);
};
function getNamedMapProvider ({ namedMapProviderCache, label, forcedFormat = null }) {
return function getNamedMapProviderMiddleware (req, res, next) {
const { user, token, cache_buster, api_key } = res.locals;
const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals;
const { template_id, layer: layerFromParams, z, x, y, format } = req.params;
const { layer: layerFromQuery } = req.query;
const params = {
user, token, cache_buster, api_key,
dbuser, dbname, dbpassword, dbhost, dbport,
template_id, layer: (layerFromQuery || layerFromParams), z, x, y, format
};
if (forcedFormat) {
params.format = forcedFormat;
params.layer = params.layer || 'all';
}
const { config, auth_token } = req.query;
namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, namedMapProvider) => {
if (err) {
err.label = label;
return next(err);
}
res.locals.mapConfigProvider = namedMapProvider;
next();
});
};
}
function getTemplate ({ label }) {
return function getTemplateMiddleware (req, res, next) {
const { mapConfigProvider } = res.locals;
@ -209,33 +138,7 @@ function prepareLayerFilterFromPreviewLayers ({ namedMapProviderCache, label })
};
}
function getTile ({ tileBackend, label }) {
return function getTileMiddleware (req, res, next) {
const { mapConfigProvider } = res.locals;
const { layer, z, x, y, format } = req.params;
const params = { layer, z, x, y, format };
tileBackend.getTile(mapConfigProvider, params, (err, tile, headers, stats) => {
req.profiler.add(stats);
req.profiler.done('render-' + format);
if (err) {
err.label = label;
return next(err);
}
if (headers) {
res.set(headers);
}
res.body = tile;
next();
});
};
}
function getStaticImageOptions ({ tablesExtentApi }) {
function getStaticImageOptions ({ tablesExtentBackend }) {
return function getStaticImageOptionsMiddleware(req, res, next) {
const { user, mapConfigProvider, template } = res.locals;
const { zoom, lon, lat, bbox } = req.query;
@ -261,7 +164,7 @@ function getStaticImageOptions ({ tablesExtentApi }) {
return next();
}
tablesExtentApi.getBounds(user, tables, (err, bounds) => {
tablesExtentBackend.getBounds(user, tables, (err, bounds) => {
if (err) {
return next();
}
@ -365,6 +268,7 @@ function getImage({ previewBackend, label }) {
res.set(headers);
}
res.statusCode = 200;
res.body = image;
next();
@ -384,6 +288,7 @@ function getImage({ previewBackend, label }) {
res.set(headers);
}
res.statusCode = 200;
res.body = image;
next();

View File

@ -0,0 +1,163 @@
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 dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const createMapStoreMapConfigProvider = require('../middlewares/map-store-map-config-provider');
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 vectorError = require('../middlewares/vector-error');
const SUPPORTED_FORMATS = {
grid_json: true,
json_torque: true,
torque_json: true,
png: true,
png32: true,
mvt: true
};
module.exports = class TileLayergroupController {
constructor (
tileBackend,
pgConnection,
mapStore,
userLimitsBackend,
layergroupAffectedTablesCache,
authBackend,
surrogateKeysCache
) {
this.tileBackend = tileBackend;
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.userLimitsBackend = userLimitsBackend;
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
this.authBackend = authBackend;
this.surrogateKeysCache = surrogateKeysCache;
}
register (mapRouter) {
// REGEXP doesn't match with `val`
const not = (val) => `(?!${val})([^\/]+?)`;
mapRouter.get([
`/:token/:z/:x/:y@:scale_factor?x.:format`,
`/:token/:z/:x/:y.:format`,
`/:token${not('static')}/:layer/:z/:x/:y.(:format)`
],
layergroupToken(),
coordinates(),
credentials(),
authorize(this.authBackend),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.TILE),
cleanUpQueryParams(),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsBackend,
this.pgConnection,
this.layergroupAffectedTablesCache
),
getTile(this.tileBackend),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
incrementSuccessMetrics(global.statsClient),
incrementErrorMetrics(global.statsClient),
tileError(),
vectorError()
);
}
};
function parseFormat (format = '') {
const prettyFormat = format.replace('.', '_');
return SUPPORTED_FORMATS[prettyFormat] ? prettyFormat : 'invalid';
}
function getStatusCode(tile, format){
return tile.length === 0 && format === 'mvt' ? 204 : 200;
}
function getTile (tileBackend) {
return function getTileMiddleware (req, res, next) {
req.profiler.start(`windshaft.${req.params.layer ? 'maplayer_tile' : 'map_tile'}`);
const { mapConfigProvider } = res.locals;
const { token } = res.locals;
const { layer, z, x, y, format } = req.params;
const params = { token, layer, z, x, y, format };
tileBackend.getTile(mapConfigProvider, params, (err, tile, headers, stats = {}) => {
req.profiler.add(stats);
if (err) {
return next(err);
}
if (headers) {
res.set(headers);
}
const formatStat = parseFormat(req.params.format);
res.statusCode = getStatusCode(tile, formatStat);
res.body = tile;
next();
});
};
}
function incrementSuccessMetrics (statsClient) {
return function incrementSuccessMetricsMiddleware (req, res, next) {
const formatStat = parseFormat(req.params.format);
statsClient.increment('windshaft.tiles.success');
statsClient.increment(`windshaft.tiles.${formatStat}.success`);
next();
};
}
function incrementErrorMetrics (statsClient) {
return function incrementErrorMetricsMiddleware (err, req, res, next) {
const formatStat = parseFormat(req.params.format);
statsClient.increment('windshaft.tiles.error');
statsClient.increment(`windshaft.tiles.${formatStat}.error`);
next(err);
};
}
function tileError () {
return function tileErrorMiddleware (err, req, res, next) {
if (err.message === 'Tile does not exist' && req.params.format === 'mvt') {
res.statusCode = 204;
return next();
}
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
let errMsg = err.message ? ( '' + err.message ) : ( '' + err );
// Rewrite mapnik parsing errors to start with layer number
const matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
if (matches) {
errMsg = `style${matches[2]}: ${matches[1]}`;
}
err.message = errMsg;
err.label = 'TILE RENDER';
next(err);
};
}

View File

@ -0,0 +1,14 @@
const _ = require('underscore');
module.exports = 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();
};
};

View File

@ -1,6 +1,6 @@
module.exports = function authorize (authApi) {
module.exports = function authorize (authBackend) {
return function authorizeMiddleware (req, res, next) {
authApi.authorize(req, res, (err, authorized) => {
authBackend.authorize(req, res, (err, authorized) => {
req.profiler.done('authorize');
if (err) {

View File

@ -0,0 +1,11 @@
module.exports = 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();
};
};

View File

@ -0,0 +1,18 @@
module.exports = function cors () {
return function corsMiddleware (req, res, next) {
const headers = [
'X-Requested-With',
'X-Prototype-Version',
'X-CSRF-Token'
];
if (req.method === 'OPTIONS') {
headers.push('Content-Type');
}
res.set("Access-Control-Allow-Origin", "*");
res.set("Access-Control-Allow-Headers", headers.join(', '));
next();
};
};

View File

@ -0,0 +1,16 @@
module.exports = 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();
});
};
};

View File

@ -0,0 +1,9 @@
module.exports = 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();
};
};

View File

@ -0,0 +1,9 @@
module.exports = function initializeStatusCode () {
return function initializeStatusCodeMiddleware (req, res, next) {
if (req.method !== 'OPTIONS') {
res.statusCode = 404;
}
next();
};
};

View File

@ -0,0 +1,39 @@
module.exports = function setLastUpdatedTimeToLayergroup () {
return function setLastUpdatedTimeToLayergroupMiddleware (req, res, next) {
const { mapConfigProvider, analysesResults } = res.locals;
const layergroup = res.body;
mapConfigProvider.createAffectedTables((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);
}

View File

@ -0,0 +1,26 @@
module.exports = 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();
});
});
};
};

View File

@ -0,0 +1,15 @@
module.exports = 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();
};
};

View File

@ -0,0 +1,14 @@
module.exports = function setMetadataToLayergroup (layergroupMetadata, includeQuery) {
return function setMetadataToLayergroupMiddleware (req, res, next) {
const { user, mapConfig, analysesResults = [], context } = res.locals;
const layergroup = res.body;
layergroupMetadata.addDataviewsAndWidgetsUrls(user, layergroup, mapConfig.obj());
layergroupMetadata.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery);
layergroupMetadata.addTurboCartoContextMetadata(layergroup, mapConfig.obj(), context);
layergroupMetadata.addAggregationContextMetadata(layergroup, mapConfig.obj(), context);
layergroupMetadata.addTileJsonMetadata(layergroup, user, mapConfig);
next();
};
};

View File

@ -1,4 +1,4 @@
const LayergroupToken = require('../models/layergroup-token');
const LayergroupToken = require('../../models/layergroup-token');
const authErrorMessageTemplate = function (signer, user) {
return `Cannot use map signature of user "${signer}" on db of user "${user}"`;
};

View File

@ -0,0 +1,22 @@
module.exports = function logger (options) {
if (!global.log4js || !options.log_format) {
return function dummyLoggerMiddleware (req, res, next) {
next();
};
}
const opts = {
level: 'info',
// Allowing for unbuffered logging is mainly
// used to avoid hanging during unit testing.
// TODO: provide an explicit teardown function instead,
// releasing any event handler or timer set by
// this component.
buffer: !options.unbuffered_logging,
// optional log format
format: options.log_format
};
const logger = global.log4js.getLogger();
return global.log4js.connectLogger(logger, opts);
};

View File

@ -0,0 +1,35 @@
module.exports = function mapError (options) {
const { addContext = false, label = 'MAPS CONTROLLER' } = options;
return function mapErrorMiddleware (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;
}

View File

@ -1,8 +1,8 @@
const MapStoreMapConfigProvider = require('../../../models/mapconfig/provider/map-store-provider');
const MapStoreMapConfigProvider = require('../../models/mapconfig/provider/map-store-provider');
module.exports = function createMapStoreMapConfigProvider (
mapStore,
userLimitsApi,
userLimitsBackend,
pgConnection,
affectedTablesCache,
forcedFormat = null
@ -26,7 +26,7 @@ module.exports = function createMapStoreMapConfigProvider (
res.locals.mapConfigProvider = new MapStoreMapConfigProvider(
mapStore,
user,
userLimitsApi,
userLimitsBackend,
pgConnection,
affectedTablesCache,
params

View File

@ -0,0 +1,32 @@
module.exports = function getNamedMapProvider ({ namedMapProviderCache, label, forcedFormat = null }) {
return function getNamedMapProviderMiddleware (req, res, next) {
const { user, token, cache_buster, api_key } = res.locals;
const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals;
const { template_id, layer: layerFromParams, z, x, y, format } = req.params;
const { layer: layerFromQuery } = req.query;
const params = {
user, token, cache_buster, api_key,
dbuser, dbname, dbpassword, dbhost, dbport,
template_id, layer: (layerFromQuery || layerFromParams), z, x, y, format
};
if (forcedFormat) {
params.format = forcedFormat;
params.layer = params.layer || 'all';
}
const { config, auth_token } = req.query;
namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, namedMapProvider) => {
if (err) {
err.label = label;
return next(err);
}
res.locals.mapConfigProvider = namedMapProvider;
next();
});
};
};

View File

@ -19,13 +19,13 @@ const RATE_LIMIT_ENDPOINTS_GROUPS = {
NAMED_TILES: 'named_tiles'
};
function rateLimit(userLimitsApi, endpointGroup = null) {
function rateLimit(userLimitsBackend, endpointGroup = null) {
if (!isRateLimitEnabled(endpointGroup)) {
return function rateLimitDisabledMiddleware(req, res, next) { next(); };
}
return function rateLimitMiddleware(req, res, next) {
userLimitsApi.getRateLimit(res.locals.user, endpointGroup, function (err, userRateLimit) {
userLimitsBackend.getRateLimit(res.locals.user, endpointGroup, function (err, userRateLimit) {
if (err) {
return next(err);
}

View File

@ -2,7 +2,7 @@ module.exports = function sendResponse () {
return function sendResponseMiddleware (req, res) {
req.profiler.done('res');
res.status(res.statusCode || 200);
res.status(res.statusCode);
if (Buffer.isBuffer(res.body)) {
return res.send(res.body);

View File

@ -0,0 +1,11 @@
const os = require('os');
module.exports = function servedByHostHeader () {
const hostname = os.hostname().split('.')[0];
return function servedByHostHeaderMiddleware (req, res, next) {
res.set('X-Served-By-Host', hostname);
next();
};
};

View File

@ -1,4 +1,4 @@
const Profiler = require('../stats/profiler_proxy');
const Profiler = require('../../stats/profiler_proxy');
const debug = require('debug')('windshaft:cartodb:stats');
const onHeaders = require('on-headers');

View File

@ -1,5 +1,5 @@
const NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
const NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider');
const NamedMapsCacheEntry = require('../../cache/model/named_maps_entry');
const NamedMapMapConfigProvider = require('../../models/mapconfig/provider/named-map-provider');
module.exports = function setSurrogateKeyHeader ({ surrogateKeysCache }) {
return function setSurrogateKeyHeaderMiddleware(req, res, next) {

View File

@ -0,0 +1,10 @@
module.exports = function syntaxError () {
return function syntaxErrorMiddleware (err, req, res, next) {
if (err.name === 'SyntaxError') {
err.http_status = 400;
err.message = `${err.name}: ${err.message}`;
}
next(err);
};
};

View File

@ -1,4 +1,4 @@
const CdbRequest = require('../models/cdb_request');
const CdbRequest = require('../../models/cdb_request');
module.exports = function user () {
const cdbRequest = new CdbRequest();

View File

@ -1,5 +1,5 @@
const fs = require('fs');
const timeoutErrorVectorTile = fs.readFileSync(__dirname + '/../../../assets/render-timeout-fallback.mvt');
const timeoutErrorVectorTile = fs.readFileSync(__dirname + '/../../../../assets/render-timeout-fallback.mvt');
module.exports = function vectorError() {
return function vectorErrorMiddleware(err, req, res, next) {

View File

@ -1,88 +1,65 @@
const { templateName } = require('../backends/template_maps');
const cors = require('../middleware/cors');
const user = require('../middleware/user');
const credentials = require('../middleware/credentials');
const rateLimit = require('../middleware/rate-limit');
const { templateName } = require('../../backends/template_maps');
const credentials = require('../middlewares/credentials');
const rateLimit = require('../middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const sendResponse = require('../middleware/send-response');
/**
* @param {AuthApi} authApi
* @param {AuthBackend} authBackend
* @param {PgConnection} pgConnection
* @param {TemplateMaps} templateMaps
* @constructor
*/
function NamedMapsAdminController(authApi, templateMaps, userLimitsApi) {
this.authApi = authApi;
function AdminTemplateController(authBackend, templateMaps, userLimitsBackend) {
this.authBackend = authBackend;
this.templateMaps = templateMaps;
this.userLimitsApi = userLimitsApi;
this.userLimitsBackend = userLimitsBackend;
}
module.exports = NamedMapsAdminController;
module.exports = AdminTemplateController;
NamedMapsAdminController.prototype.register = function (app) {
const { base_url_templated: templateBasePath } = app;
AdminTemplateController.prototype.register = function (templateRouter) {
templateRouter.options(`/:template_id`);
app.post(
`${templateBasePath}/`,
cors(),
user(),
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' }),
authorizedByAPIKey({ authApi: this.authApi, action: 'create', label: 'POST TEMPLATE' }),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_CREATE),
createTemplate({ templateMaps: this.templateMaps }),
sendResponse()
createTemplate({ templateMaps: this.templateMaps })
);
app.put(
`${templateBasePath}/:template_id`,
cors(),
user(),
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' }),
authorizedByAPIKey({ authApi: this.authApi, action: 'update', label: 'PUT TEMPLATE' }),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_UPDATE),
updateTemplate({ templateMaps: this.templateMaps }),
sendResponse()
updateTemplate({ templateMaps: this.templateMaps })
);
app.get(
`${templateBasePath}/:template_id`,
cors(),
user(),
templateRouter.get(
`/:template_id`,
credentials(),
authorizedByAPIKey({ authApi: this.authApi, action: 'get', label: 'GET TEMPLATE' }),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_GET),
retrieveTemplate({ templateMaps: this.templateMaps }),
sendResponse()
authorizedByAPIKey({ authBackend: this.authBackend, action: 'get', label: 'GET TEMPLATE' }),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_GET),
retrieveTemplate({ templateMaps: this.templateMaps })
);
app.delete(
`${templateBasePath}/:template_id`,
cors(),
user(),
templateRouter.delete(
`/:template_id`,
credentials(),
authorizedByAPIKey({ authApi: this.authApi, action: 'delete', label: 'DELETE TEMPLATE' }),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_DELETE),
destroyTemplate({ templateMaps: this.templateMaps }),
sendResponse()
authorizedByAPIKey({ authBackend: this.authBackend, action: 'delete', label: 'DELETE TEMPLATE' }),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_DELETE),
destroyTemplate({ templateMaps: this.templateMaps })
);
app.get(
`${templateBasePath}/`,
cors(),
user(),
templateRouter.get(
`/`,
credentials(),
authorizedByAPIKey({ authApi: this.authApi, action: 'list', label: 'GET TEMPLATE LIST' }),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_LIST),
listTemplates({ templateMaps: this.templateMaps }),
sendResponse()
);
app.options(
`${templateBasePath}/:template_id`,
cors('Content-Type')
authorizedByAPIKey({ authBackend: this.authBackend, action: 'list', label: 'GET TEMPLATE LIST' }),
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_LIST),
listTemplates({ templateMaps: this.templateMaps })
);
};
@ -98,11 +75,11 @@ function checkContentType ({ action, label }) {
};
}
function authorizedByAPIKey ({ authApi, action, label }) {
function authorizedByAPIKey ({ authBackend, action, label }) {
return function authorizedByAPIKeyMiddleware (req, res, next) {
const { user } = res.locals;
authApi.authorizedByAPIKey(user, res, (err, authenticated, apikey) => {
authBackend.authorizedByAPIKey(user, res, (err, authenticated, apikey) => {
if (err) {
return next(err);
}
@ -138,6 +115,7 @@ function createTemplate ({ templateMaps }) {
return next(err);
}
res.statusCode = 200;
res.body = { template_id: templateId };
next();
@ -156,6 +134,7 @@ function updateTemplate ({ templateMaps }) {
return next(err);
}
res.statusCode = 200;
res.body = { template_id: templateId };
next();
@ -184,6 +163,7 @@ function retrieveTemplate ({ templateMaps }) {
// so we remove it before returning to the user
delete template.auth_id;
res.statusCode = 200;
res.body = { template };
next();
@ -222,6 +202,7 @@ function listTemplates ({ templateMaps }) {
return next(err);
}
res.statusCode = 200;
res.body = { template_ids: templateIds };
next();

View File

@ -0,0 +1,219 @@
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const initProfiler = require('../middlewares/init-profiler');
const checkJsonContentType = require('../middlewares/check-json-content-type');
const incrementMapViewCount = require('../middlewares/increment-map-view-count');
const augmentLayergroupData = require('../middlewares/augment-layergroup-data');
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 lastUpdatedTimeLayergroup = require('../middlewares/last-updated-time-layergroup');
const layerStats = require('../middlewares/layer-stats');
const layergroupIdHeader = require('../middlewares/layergroup-id-header');
const layergroupMetadata = require('../middlewares/layergroup-metadata');
const mapError = require('../middlewares/map-error');
const NamedMapMapConfigProvider = require('../../models/mapconfig/provider/named-map-provider');
const CreateLayergroupMapConfigProvider = require('../../models/mapconfig/provider/create-layergroup-provider');
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 = 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.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 () {
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,
userLimitsBackend,
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,
userLimitsBackend,
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, userLimitsBackend, pgConnection, affectedTablesCache) {
return function instantiateLayergroupMiddleware (req, res, next) {
const { user, mapConfig, rendererParams } = res.locals;
const mapConfigProvider = new CreateLayergroupMapConfigProvider(
mapConfig,
user,
userLimitsBackend,
pgConnection,
affectedTablesCache,
rendererParams
);
mapBackend.createLayergroup(mapConfig, rendererParams, mapConfigProvider, (err, layergroup) => {
req.profiler.done('createLayergroup');
if (err) {
return next(err);
}
res.statusCode = 200;
res.body = layergroup;
const { mapConfigProvider } = res.locals;
res.locals.analysesResults = mapConfigProvider.analysesResults;
res.locals.template = mapConfigProvider.template;
res.locals.context = mapConfigProvider.context;
next();
});
};
}

View File

@ -0,0 +1,64 @@
const { Router: router } = require('express');
const NamedMapController = require('./named-template-controller');
const AdminTemplateController = require('./admin-template-controller');
const TileTemplateController = require('./tile-template-controller');
module.exports = class TemplateRouter {
constructor ({ collaborators }) {
const {
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
surrogateKeysCache,
userLimitsBackend,
layergroupAffectedTablesCache,
mapConfigAdapter,
statsBackend,
authBackend,
layergroupMetadata,
namedMapProviderCache,
tileBackend,
} = collaborators;
this.namedMapController = new NamedMapController(
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
surrogateKeysCache,
userLimitsBackend,
layergroupAffectedTablesCache,
mapConfigAdapter,
statsBackend,
authBackend,
layergroupMetadata
);
this.tileTemplateController = new TileTemplateController(
namedMapProviderCache,
tileBackend,
surrogateKeysCache,
pgConnection,
authBackend,
userLimitsBackend
);
this.adminTemplateController = new AdminTemplateController(
authBackend,
templateMaps,
userLimitsBackend
);
}
register (apiRouter, templatePaths) {
const templateRouter = router();
this.namedMapController.register(templateRouter);
this.tileTemplateController.register(templateRouter);
this.adminTemplateController.register(templateRouter);
templatePaths.forEach(path => apiRouter.use(path, templateRouter));
}
};

View File

@ -0,0 +1,92 @@
const coordinates = require('../middlewares/coordinates');
const cleanUpQueryParams = require('../middlewares/clean-up-query-params');
const credentials = require('../middlewares/credentials');
const dbConnSetup = require('../middlewares/db-conn-setup');
const authorize = require('../middlewares/authorize');
const namedMapProvider = require('../middlewares/named-map-provider');
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 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 = TileTemplateController;
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()
);
};
function getTile ({ tileBackend, label }) {
return function getTileMiddleware (req, res, next) {
const { mapConfigProvider } = res.locals;
const { layer, z, x, y, format } = req.params;
const params = { layer, z, x, y, format };
tileBackend.getTile(mapConfigProvider, params, (err, tile, headers, stats) => {
req.profiler.add(stats);
req.profiler.done('render-' + format);
if (err) {
err.label = label;
return next(err);
}
if (headers) {
res.set(headers);
}
res.statusCode = 200;
res.body = tile;
next();
});
};
}
function setContentTypeHeader () {
return function setContentTypeHeaderMiddleware(req, res, next) {
res.set('Content-Type', res.get('content-type') || res.get('Content-Type') || 'image/png');
next();
};
}

View File

@ -7,16 +7,16 @@ var _ = require('underscore'); // AUTH_FALLBACK
* @param {MapStore} mapStore
* @param {TemplateMaps} templateMaps
* @constructor
* @type {AuthApi}
* @type {AuthBackend}
*/
function AuthApi(pgConnection, metadataBackend, mapStore, templateMaps) {
function AuthBackend(pgConnection, metadataBackend, mapStore, templateMaps) {
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.mapStore = mapStore;
this.templateMaps = templateMaps;
}
module.exports = AuthApi;
module.exports = AuthBackend;
// Check if the user is authorized by a signer
//
@ -25,7 +25,7 @@ module.exports = AuthApi;
// null if the request is not signed by anyone
// or will be a string cartodb username otherwise.
//
AuthApi.prototype.authorizedBySigner = function(req, res, callback) {
AuthBackend.prototype.authorizedBySigner = function(req, res, callback) {
if ( ! res.locals.token || ! res.locals.signer ) {
return callback(null, false); // no signer requested
}
@ -60,7 +60,7 @@ function isValidApiKey(apikey) {
// @param callback function(err, authorized)
// NOTE: authorized is expected to be 0 or 1 (integer)
//
AuthApi.prototype.authorizedByAPIKey = function(user, res, callback) {
AuthBackend.prototype.authorizedByAPIKey = function(user, res, callback) {
const apikeyToken = res.locals.api_key;
const basicAuthUsername = res.locals.basicAuthUsername;
@ -160,7 +160,7 @@ function usernameMatches (basicAuthUsername, requestUsername) {
* @param res - standard res object. Contains the auth parameters in locals
* @param callback function(err, allowed) is access allowed not?
*/
AuthApi.prototype.authorize = function(req, res, callback) {
AuthBackend.prototype.authorize = function(req, res, callback) {
var user = res.locals.user;
this.authorizedByAPIKey(user, res, (err, isAuthorizedByApikey) => {

View File

@ -2,11 +2,11 @@ var _ = require('underscore');
var step = require('step');
var AnalysisFilter = require('../models/filter/analysis');
function FilterStatsApi(pgQueryRunner) {
function FilterStatsBackends(pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = FilterStatsApi;
module.exports = FilterStatsBackends;
function getEstimatedRows(pgQueryRunner, username, query, callback) {
pgQueryRunner.run(username, "EXPLAIN (FORMAT JSON)"+query, function(err, result_rows) {
@ -23,7 +23,7 @@ function getEstimatedRows(pgQueryRunner, username, query, callback) {
});
}
FilterStatsApi.prototype.getFilterStats = function (username, unfiltered_query, filters, callback) {
FilterStatsBackends.prototype.getFilterStats = function (username, unfiltered_query, filters, callback) {
var stats = {};
var self = this;
step(

View File

@ -1,10 +1,10 @@
var SubstitutionTokens = require('../utils/substitution-tokens');
function OverviewsMetadataApi(pgQueryRunner) {
function OverviewsMetadataBackend(pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = OverviewsMetadataApi;
module.exports = OverviewsMetadataBackend;
function prepareSql(sql) {
return sql && SubstitutionTokens.replace(sql, {
@ -15,7 +15,7 @@ function prepareSql(sql) {
});
}
OverviewsMetadataApi.prototype.getOverviewsMetadata = function (username, sql, callback) {
OverviewsMetadataBackend.prototype.getOverviewsMetadata = function (username, sql, callback) {
// FIXME: Currently using internal function _cdb_schema_name
// CDB_Overviews should provide the schema information directly.
var query = 'SELECT *, _cdb_schema_name(base_table)' +

View File

@ -1,8 +1,8 @@
function TablesExtentApi(pgQueryRunner) {
function TablesExtentBackend(pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = TablesExtentApi;
module.exports = TablesExtentBackend;
/**
* Given a username and a list of tables it will return the estimated extent in SRID 4326 for all the tables based on
@ -13,7 +13,7 @@ module.exports = TablesExtentApi;
* `table_name` format as valid input
* @param {Function} callback function(err, result) {Object} result with `west`, `south`, `east`, `north`
*/
TablesExtentApi.prototype.getBounds = function (username, tables, callback) {
TablesExtentBackend.prototype.getBounds = function (username, tables, callback) {
var estimatedExtentSQLs = tables.map(function(table) {
return "ST_EstimatedExtent('" + table.schema_name + "', '" + table.table_name + "', 'the_geom_webmercator')";
});

View File

@ -5,9 +5,9 @@ var step = require('step');
* @param metadataBackend
* @param options
* @constructor
* @type {UserLimitsApi}
* @type {UserLimitsBackend}
*/
function UserLimitsApi(metadataBackend, options) {
function UserLimitsBackend(metadataBackend, options) {
this.metadataBackend = metadataBackend;
this.options = options || {};
this.options.limits = this.options.limits || {};
@ -15,9 +15,9 @@ function UserLimitsApi(metadataBackend, options) {
this.preprareRateLimit();
}
module.exports = UserLimitsApi;
module.exports = UserLimitsBackend;
UserLimitsApi.prototype.getRenderLimits = function (username, apiKey, callback) {
UserLimitsBackend.prototype.getRenderLimits = function (username, apiKey, callback) {
var self = this;
var limits = {
@ -40,7 +40,7 @@ UserLimitsApi.prototype.getRenderLimits = function (username, apiKey, callback)
});
};
UserLimitsApi.prototype.getTimeoutRenderLimit = function (username, apiKey, callback) {
UserLimitsBackend.prototype.getTimeoutRenderLimit = function (username, apiKey, callback) {
var self = this;
step(
@ -80,12 +80,12 @@ UserLimitsApi.prototype.getTimeoutRenderLimit = function (username, apiKey, call
);
};
UserLimitsApi.prototype.preprareRateLimit = function () {
UserLimitsBackend.prototype.preprareRateLimit = function () {
if (this.options.limits.rateLimitsEnabled) {
this.metadataBackend.loadRateLimitsScript();
}
};
UserLimitsApi.prototype.getRateLimit = function (user, endpointGroup, callback) {
UserLimitsBackend.prototype.getRateLimit = function (user, endpointGroup, callback) {
this.metadataBackend.getRateLimit(user, 'maps', endpointGroup, callback);
};

View File

@ -10,14 +10,14 @@ function NamedMapProviderCache(
templateMaps,
pgConnection,
metadataBackend,
userLimitsApi,
userLimitsBackend,
mapConfigAdapter,
affectedTablesCache
) {
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.userLimitsApi = userLimitsApi;
this.userLimitsBackend = userLimitsBackend;
this.mapConfigAdapter = mapConfigAdapter;
this.affectedTablesCache = affectedTablesCache;
@ -36,7 +36,7 @@ NamedMapProviderCache.prototype.get = function(user, templateId, config, authTok
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsApi,
this.userLimitsBackend,
this.mapConfigAdapter,
this.affectedTablesCache,
user,

View File

@ -1,8 +0,0 @@
module.exports = {
Analyses: require('./analyses'),
Layergroup: require('./layergroup'),
Map: require('./map'),
NamedMaps: require('./named_maps'),
NamedMapsAdmin: require('./named_maps_admin'),
ServerInfo: require('./server_info')
};

View File

@ -1,75 +0,0 @@
const cors = require('../../middleware/cors');
const user = require('../../middleware/user');
const layergroupToken = require('../../middleware/layergroup-token');
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 rateLimit = require('../../middleware/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const sendResponse = require('../../middleware/send-response');
const dbParamsFromResLocals = require('../../utils/database-params');
module.exports = class AnalysisController {
constructor (
analysisStatusBackend,
pgConnection,
mapStore,
userLimitsApi,
layergroupAffectedTablesCache,
authApi,
surrogateKeysCache
) {
this.analysisStatusBackend = analysisStatusBackend;
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.userLimitsApi = userLimitsApi;
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
this.authApi = authApi;
this.surrogateKeysCache = surrogateKeysCache;
}
register (app) {
const { base_url_mapconfig: mapConfigBasePath } = app;
app.get(
`${mapConfigBasePath}/:token/analysis/node/:nodeId`,
cors(),
user(),
layergroupToken(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.ANALYSIS),
cleanUpQueryParams(),
analysisNodeStatus(this.analysisStatusBackend),
sendResponse()
);
}
};
function analysisNodeStatus (analysisStatusBackend) {
return function analysisNodeStatusMiddleware(req, res, next) {
const { nodeId } = req.params;
const dbParams = dbParamsFromResLocals(res.locals);
analysisStatusBackend.getNodeStatus(nodeId, dbParams, (err, nodeStatus, stats = {}) => {
req.profiler.add(stats);
if (err) {
err.label = 'GET NODE STATUS';
return next(err);
}
res.set({
'Cache-Control': 'public,max-age=5',
'Last-Modified': new Date().toUTCString()
});
res.body = nodeStatus;
next();
});
};
}

View File

@ -1,114 +0,0 @@
const DataviewBackend = require('../../backends/dataview');
const AnalysisStatusBackend = require('../../backends/analysis-status');
const TileController = require('./tile');
const AttributesController = require('./attributes');
const StaticController = require('./static');
const DataviewController = require('./dataview');
const AnalysisController = require('./analysis');
/**
* @param {prepareContext} prepareContext
* @param {PgConnection} pgConnection
* @param {MapStore} mapStore
* @param {TileBackend} tileBackend
* @param {PreviewBackend} previewBackend
* @param {AttributesBackend} attributesBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsApi} userLimitsApi
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {AnalysisBackend} analysisBackend
* @constructor
*/
function LayergroupController(
pgConnection,
mapStore,
tileBackend,
previewBackend,
attributesBackend,
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
analysisBackend,
authApi
) {
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.attributesBackend = attributesBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsApi = userLimitsApi;
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
this.dataviewBackend = new DataviewBackend(analysisBackend);
this.analysisStatusBackend = new AnalysisStatusBackend();
this.authApi = authApi;
}
module.exports = LayergroupController;
LayergroupController.prototype.register = function(app) {
const tileController = new TileController(
this.tileBackend,
this.pgConnection,
this.mapStore,
this.userLimitsApi,
this.layergroupAffectedTablesCache,
this.authApi,
this.surrogateKeysCache
);
tileController.register(app);
const attributesController = new AttributesController(
this.attributesBackend,
this.pgConnection,
this.mapStore,
this.userLimitsApi,
this.layergroupAffectedTablesCache,
this.authApi,
this.surrogateKeysCache
);
attributesController.register(app);
const staticController = new StaticController(
this.previewBackend,
this.pgConnection,
this.mapStore,
this.userLimitsApi,
this.layergroupAffectedTablesCache,
this.authApi,
this.surrogateKeysCache
);
staticController.register(app);
const dataviewController = new DataviewController(
this.dataviewBackend,
this.pgConnection,
this.mapStore,
this.userLimitsApi,
this.layergroupAffectedTablesCache,
this.authApi,
this.surrogateKeysCache
);
dataviewController.register(app);
const analysisController = new AnalysisController(
this.analysisStatusBackend,
this.pgConnection,
this.mapStore,
this.userLimitsApi,
this.layergroupAffectedTablesCache,
this.authApi,
this.surrogateKeysCache
);
analysisController.register(app);
};

View File

@ -1,234 +0,0 @@
const cors = require('../../middleware/cors');
const user = require('../../middleware/user');
const layergroupToken = require('../../middleware/layergroup-token');
const coordinates = require('../../middleware/coordinates');
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 rateLimit = require('../../middleware/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
const createMapStoreMapConfigProvider = require('./middlewares/map-store-map-config-provider');
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 vectorError = require('../../middleware/vector-error');
const SUPPORTED_FORMATS = {
grid_json: true,
json_torque: true,
torque_json: true,
png: true,
png32: true,
mvt: true
};
module.exports = class TileController {
constructor (
tileBackend,
pgConnection,
mapStore,
userLimitsApi,
layergroupAffectedTablesCache,
authApi,
surrogateKeysCache
) {
this.tileBackend = tileBackend;
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.userLimitsApi = userLimitsApi;
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
this.authApi = authApi;
this.surrogateKeysCache = surrogateKeysCache;
}
register (app) {
const { base_url_mapconfig: mapConfigBasePath } = app;
app.get(
`${mapConfigBasePath}/:token/:z/:x/:y@:scale_factor?x.:format`,
cors(),
user(),
layergroupToken(),
coordinates(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.TILE),
cleanUpQueryParams(),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
getTile(this.tileBackend, 'map_tile'),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
incrementSuccessMetrics(global.statsClient),
incrementErrorMetrics(global.statsClient),
tileError(),
vectorError(),
sendResponse()
);
app.get(
`${mapConfigBasePath}/:token/:z/:x/:y.:format`,
cors(),
user(),
layergroupToken(),
coordinates(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.TILE),
cleanUpQueryParams(),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
getTile(this.tileBackend, 'map_tile'),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
incrementSuccessMetrics(global.statsClient),
incrementErrorMetrics(global.statsClient),
tileError(),
vectorError(),
sendResponse()
);
app.get(
`${mapConfigBasePath}/:token/:layer/:z/:x/:y.(:format)`,
distinguishLayergroupFromStaticRoute(),
cors(),
user(),
layergroupToken(),
coordinates(),
credentials(),
authorize(this.authApi),
dbConnSetup(this.pgConnection),
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.TILE),
cleanUpQueryParams(),
createMapStoreMapConfigProvider(
this.mapStore,
this.userLimitsApi,
this.pgConnection,
this.layergroupAffectedTablesCache
),
getTile(this.tileBackend, 'maplayer_tile'),
cacheControlHeader(),
cacheChannelHeader(),
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
lastModifiedHeader(),
incrementSuccessMetrics(global.statsClient),
incrementErrorMetrics(global.statsClient),
tileError(),
vectorError(),
sendResponse()
);
}
};
function distinguishLayergroupFromStaticRoute () {
return function distinguishLayergroupFromStaticRouteMiddleware(req, res, next) {
if (req.params.token === 'static') {
return next('route');
}
next();
};
}
function parseFormat (format = '') {
const prettyFormat = format.replace('.', '_');
return SUPPORTED_FORMATS[prettyFormat] ? prettyFormat : 'invalid';
}
function getStatusCode(tile, format){
return tile.length === 0 && format === 'mvt' ? 204 : 200;
}
function getTile (tileBackend, profileLabel = 'tile') {
return function getTileMiddleware (req, res, next) {
req.profiler.start(`windshaft.${profileLabel}`);
const { mapConfigProvider } = res.locals;
const { token } = res.locals;
const { layer, z, x, y, format } = req.params;
const params = { token, layer, z, x, y, format };
tileBackend.getTile(mapConfigProvider, params, (err, tile, headers, stats = {}) => {
req.profiler.add(stats);
if (err) {
return next(err);
}
if (headers) {
res.set(headers);
}
const formatStat = parseFormat(req.params.format);
res.statusCode = getStatusCode(tile, formatStat);
res.body = tile;
next();
});
};
}
function incrementSuccessMetrics (statsClient) {
return function incrementSuccessMetricsMiddleware (req, res, next) {
const formatStat = parseFormat(req.params.format);
statsClient.increment('windshaft.tiles.success');
statsClient.increment(`windshaft.tiles.${formatStat}.success`);
next();
};
}
function incrementErrorMetrics (statsClient) {
return function incrementErrorMetricsMiddleware (err, req, res, next) {
const formatStat = parseFormat(req.params.format);
statsClient.increment('windshaft.tiles.error');
statsClient.increment(`windshaft.tiles.${formatStat}.error`);
next(err);
};
}
function tileError () {
return function tileErrorMiddleware (err, req, res, next) {
if (err.message === 'Tile does not exist' && req.params.format === 'mvt') {
res.statusCode = 204;
return next();
}
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
let errMsg = err.message ? ( '' + err.message ) : ( '' + err );
// Rewrite mapnik parsing errors to start with layer number
const matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
if (matches) {
errMsg = `style${matches[2]}: ${matches[1]}`;
}
err.message = errMsg;
err.label = 'TILE RENDER';
next(err);
};
}

View File

@ -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.createAffectedTables((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;
}

View File

@ -1,14 +0,0 @@
module.exports = function cors (extraHeaders) {
return function corsMiddleware (req, res, next) {
let baseHeaders = "X-Requested-With, X-Prototype-Version, X-CSRF-Token";
if(extraHeaders) {
baseHeaders += ", " + extraHeaders;
}
res.set("Access-Control-Allow-Origin", "*");
res.set("Access-Control-Allow-Headers", baseHeaders);
next();
};
};

View File

@ -2,9 +2,9 @@ var step = require('step');
var queue = require('queue-async');
var _ = require('underscore');
function MapConfigOverviewsAdapter(overviewsMetadataApi, filterStatsApi) {
this.overviewsMetadataApi = overviewsMetadataApi;
this.filterStatsApi = filterStatsApi;
function MapConfigOverviewsAdapter(overviewsMetadataBackend, filterStatsBackend) {
this.overviewsMetadataBackend = overviewsMetadataBackend;
this.filterStatsBackend = filterStatsBackend;
}
module.exports = MapConfigOverviewsAdapter;
@ -25,7 +25,7 @@ MapConfigOverviewsAdapter.prototype.getMapConfig = function(user, requestMapConf
if ( layer.type !== 'mapnik' && layer.type !== 'cartodb' ) {
return done(null, layer);
}
self.overviewsMetadataApi.getOverviewsMetadata(user, layer.options.sql, function(err, metadata){
self.overviewsMetadataBackend.getOverviewsMetadata(user, layer.options.sql, function(err, metadata){
if (err) {
done(err, layer);
} else {
@ -53,7 +53,7 @@ MapConfigOverviewsAdapter.prototype.getMapConfig = function(user, requestMapConf
function collectStatsData(err, filters, unfiltered_query) {
var next_step = this;
if ( filters ) {
self.filterStatsApi.getFilterStats(
self.filterStatsBackend.getFilterStats(
user,
unfiltered_query, filters,
function(err, stats) {

View File

@ -7,16 +7,23 @@ const QueryTables = require('cartodb-query-tables');
/**
* @param {MapConfig} mapConfig
* @param {String} user
* @param {UserLimitsApi} userLimitsApi
* @param {UserLimitsBackend} userLimitsBackend
* @param {Object} params
* @constructor
* @type {CreateLayergroupMapConfigProvider}
*/
function CreateLayergroupMapConfigProvider(mapConfig, user, userLimitsApi, pgConnection, affectedTablesCache, params) {
function CreateLayergroupMapConfigProvider(
mapConfig,
user,
userLimitsBackend,
pgConnection,
affectedTablesCache,
params
) {
this.mapConfig = mapConfig;
this.user = user;
this.userLimitsApi = userLimitsApi;
this.userLimitsBackend = userLimitsBackend;
this.pgConnection = pgConnection;
this.affectedTablesCache = affectedTablesCache;
this.params = params;
@ -36,7 +43,7 @@ CreateLayergroupMapConfigProvider.prototype.getMapConfig = function(callback) {
step(
function prepareContextLimits() {
self.userLimitsApi.getRenderLimits(self.user, self.params.api_key, this);
self.userLimitsBackend.getRenderLimits(self.user, self.params.api_key, this);
},
function handleRenderLimits(err, renderLimits) {
assert.ifError(err);

View File

@ -7,15 +7,15 @@ const QueryTables = require('cartodb-query-tables');
/**
* @param {MapStore} mapStore
* @param {String} user
* @param {UserLimitsApi} userLimitsApi
* @param {UserLimitsBackend} userLimitsBackend
* @param {Object} params
* @constructor
* @type {MapStoreMapConfigProvider}
*/
function MapStoreMapConfigProvider(mapStore, user, userLimitsApi, pgConnection, affectedTablesCache, params) {
function MapStoreMapConfigProvider(mapStore, user, userLimitsBackend, pgConnection, affectedTablesCache, params) {
this.mapStore = mapStore;
this.user = user;
this.userLimitsApi = userLimitsApi;
this.userLimitsBackend = userLimitsBackend;
this.pgConnection = pgConnection;
this.affectedTablesCache = affectedTablesCache;
this.token = params.token;
@ -38,7 +38,7 @@ MapStoreMapConfigProvider.prototype.getMapConfig = function(callback) {
step(
function prepareContextLimits() {
self.userLimitsApi.getRenderLimits(self.user, self.params.api_key, this);
self.userLimitsBackend.getRenderLimits(self.user, self.params.api_key, this);
},
function handleRenderLimits(err, renderLimits) {
assert.ifError(err);

View File

@ -15,7 +15,7 @@ function NamedMapMapConfigProvider(
templateMaps,
pgConnection,
metadataBackend,
userLimitsApi,
userLimitsBackend,
mapConfigAdapter,
affectedTablesCache,
owner,
@ -27,7 +27,7 @@ function NamedMapMapConfigProvider(
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.userLimitsApi = userLimitsApi;
this.userLimitsBackend = userLimitsBackend;
this.mapConfigAdapter = mapConfigAdapter;
this.owner = owner;
@ -125,7 +125,7 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
function prepareContextLimits(err, _mapConfig) {
assert.ifError(err);
mapConfig = _mapConfig;
self.userLimitsApi.getRenderLimits(self.owner, self.params.api_key, this);
self.userLimitsBackend.getRenderLimits(self.owner, self.params.api_key, this);
},
function cacheAndReturnMapConfig(err, renderLimits) {
self.err = err;

View File

@ -1,4 +1,4 @@
var HealthCheck = require('../monitoring/health_check');
var HealthCheck = require('./monitoring/health_check');
var WELCOME_MSG = "This is the CartoDB Maps API, " +
"see the documentation at http://docs.cartodb.com/cartodb-platform/maps-api.html";
@ -12,10 +12,10 @@ function ServerInfoController(versions) {
module.exports = ServerInfoController;
ServerInfoController.prototype.register = function(app) {
app.get('/health', this.health.bind(this));
app.get('/', this.welcome.bind(this));
app.get('/version', this.version.bind(this));
ServerInfoController.prototype.register = function(monitorRouter) {
monitorRouter.get('/health', this.health.bind(this));
monitorRouter.get('/', this.welcome.bind(this));
monitorRouter.get('/version', this.version.bind(this));
};
ServerInfoController.prototype.welcome = function(req, res) {

View File

@ -1,273 +1,39 @@
var express = require('express');
var bodyParser = require('body-parser');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var _ = require('underscore');
const _ = require('underscore');
const express = require('express');
const windshaft = require('windshaft');
const { mapnik } = windshaft;
var controller = require('./controllers');
const jsonReplacer = require('./utils/json-replacer');
var SurrogateKeysCache = require('./cache/surrogate_keys_cache');
var NamedMapsCacheEntry = require('./cache/model/named_maps_entry');
var VarnishHttpCacheBackend = require('./cache/backend/varnish_http');
var FastlyCacheBackend = require('./cache/backend/fastly');
const ApiRouter = require('./api/api-router');
const ServerInfoController = require('./server-info-controller');
var StatsClient = require('./stats/client');
const stats = require('./middleware/stats');
const StatsClient = require('./stats/client');
var RendererStatsReporter = require('./stats/reporter/renderer');
module.exports = function createServer (serverOptions) {
validateOptions(serverOptions);
var windshaft = require('windshaft');
var mapnik = windshaft.mapnik;
var TemplateMaps = require('./backends/template_maps.js');
var OverviewsMetadataApi = require('./api/overviews_metadata_api');
var FilterStatsApi = require('./api/filter_stats_api');
var UserLimitsApi = require('./api/user_limits_api');
var AuthApi = require('./api/auth_api');
var LayergroupAffectedTablesCache = require('./cache/layergroup_affected_tables');
var NamedMapProviderCache = require('./cache/named_map_provider_cache');
var PgQueryRunner = require('./backends/pg_query_runner');
var PgConnection = require('./backends/pg_connection');
var AnalysisBackend = require('./backends/analysis');
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
var SqlWrapMapConfigAdapter = require('./models/mapconfig/adapter/sql-wrap-mapconfig-adapter');
var MapConfigNamedLayersAdapter = require('./models/mapconfig/adapter/mapconfig-named-layers-adapter');
var MapConfigBufferSizeAdapter = require('./models/mapconfig/adapter/mapconfig-buffer-size-adapter');
var AnalysisMapConfigAdapter = require('./models/mapconfig/adapter/analysis-mapconfig-adapter');
var MapConfigOverviewsAdapter = require('./models/mapconfig/adapter/mapconfig-overviews-adapter');
var TurboCartoAdapter = require('./models/mapconfig/adapter/turbo-carto-adapter');
var DataviewsWidgetsAdapter = require('./models/mapconfig/adapter/dataviews-widgets-adapter');
var AggregationMapConfigAdapter = require('./models/mapconfig/adapter/aggregation-mapconfig-adapter');
var MapConfigAdapter = require('./models/mapconfig/adapter');
var StatsBackend = require('./backends/stats');
const lzmaMiddleware = require('./middleware/lzma');
const errorMiddleware = require('./middleware/error-middleware');
module.exports = function(serverOptions) {
// Make stats client globally accessible
global.statsClient = StatsClient.getInstance(serverOptions.statsd);
var redisPool = new RedisPool(_.defaults(global.environment.redis, {
name: 'windshaft-server',
unwatchOnRelease: false,
noReadyCheck: true
}));
redisPool.on('status', function(status) {
var keyPrefix = 'windshaft.redis-pool.' + status.name + '.db' + status.db + '.';
global.statsClient.gauge(keyPrefix + 'count', status.count);
global.statsClient.gauge(keyPrefix + 'unused', status.unused);
global.statsClient.gauge(keyPrefix + 'waiting', status.waiting);
});
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var pgQueryRunner = new PgQueryRunner(pgConnection);
var overviewsMetadataApi = new OverviewsMetadataApi(pgQueryRunner);
var filterStatsApi = new FilterStatsApi(pgQueryRunner);
var userLimitsApi = new UserLimitsApi(metadataBackend, {
limits: {
cacheOnTimeout: serverOptions.renderer.mapnik.limits.cacheOnTimeout || false,
render: serverOptions.renderer.mapnik.limits.render || 0,
rateLimitsEnabled: global.environment.enabledFeatures.rateLimitsEnabled
}
});
var templateMaps = new TemplateMaps(redisPool, {
max_user_templates: global.environment.maxUserTemplates
});
var surrogateKeysCache = new SurrogateKeysCache(surrogateKeysCacheBackends(serverOptions));
function invalidateNamedMap (owner, templateName) {
var startTime = Date.now();
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
var logMessage = JSON.stringify({
username: owner,
type: 'named_map_invalidation',
elapsed: Date.now() - startTime,
error: !!err ? JSON.stringify(err.message) : undefined
});
if (err) {
global.logger.warn(logMessage);
} else {
global.logger.info(logMessage);
}
});
}
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, invalidateNamedMap);
});
serverOptions.grainstore.mapnik_version = mapnikVersion(serverOptions);
validateOptions(serverOptions);
bootstrapFonts(serverOptions);
// initialize express server
var app = bootstrap(serverOptions);
// Extend windshaft with all the elements of the options object
_.extend(app, serverOptions);
const app = express();
var mapStore = new windshaft.storage.MapStore({
pool: redisPool,
expire_time: serverOptions.grainstore.default_layergroup_ttl
});
app.enable('jsonp callback');
app.disable('x-powered-by');
app.disable('etag');
app.set('json replacer', jsonReplacer());
var onTileErrorStrategy;
if (global.environment.enabledFeatures.onTileErrorStrategy !== false) {
onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(err, tile, headers, stats, format, callback) {
const apiRouter = new ApiRouter({ serverOptions, environmentOptions: global.environment });
apiRouter.register(app);
function isRenderTimeoutError (err) {
return err.message === 'Render timed out';
}
const versions = getAndValidateVersions(serverOptions);
function isDatasourceTimeoutError (err) {
return err.message && err.message.match(/canceling statement due to statement timeout/i);
}
function isTimeoutError (err) {
return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
}
function isRasterFormat (format) {
return format === 'png' || format === 'jpg';
}
if (isTimeoutError(err) && isRasterFormat(format)) {
return callback(null, timeoutErrorTile, {
'Content-Type': 'image/png',
}, {});
} else {
return callback(err, tile, headers, stats);
}
};
}
var rendererFactory = new windshaft.renderer.Factory({
onTileErrorStrategy: onTileErrorStrategy,
mapnik: {
redisPool: redisPool,
grainstore: serverOptions.grainstore,
mapnik: serverOptions.renderer.mapnik
},
http: serverOptions.renderer.http,
mvt: serverOptions.renderer.mvt
});
// initialize render cache
var rendererCacheOpts = _.defaults(serverOptions.renderCache || {}, {
ttl: 60000, // 60 seconds TTL by default
statsInterval: 60000 // reports stats every milliseconds defined here
});
var rendererCache = new windshaft.cache.RendererCache(rendererFactory, rendererCacheOpts);
var rendererStatsReporter = new RendererStatsReporter(rendererCache, rendererCacheOpts.statsInterval);
rendererStatsReporter.start();
var attributesBackend = new windshaft.backend.Attributes();
var previewBackend = new windshaft.backend.Preview(rendererCache);
var tileBackend = new windshaft.backend.Tile(rendererCache);
var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
var mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);
var analysisBackend = new AnalysisBackend(metadataBackend, serverOptions.analysis);
var statsBackend = new StatsBackend();
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
var mapConfigAdapter = new MapConfigAdapter(
new MapConfigNamedLayersAdapter(templateMaps, pgConnection),
new MapConfigBufferSizeAdapter(),
new SqlWrapMapConfigAdapter(),
new DataviewsWidgetsAdapter(),
new AnalysisMapConfigAdapter(analysisBackend),
new AggregationMapConfigAdapter(pgConnection),
new MapConfigOverviewsAdapter(overviewsMetadataApi, filterStatsApi),
new TurboCartoAdapter()
);
var namedMapProviderCache = new NamedMapProviderCache(
templateMaps,
pgConnection,
metadataBackend,
userLimitsApi,
mapConfigAdapter,
layergroupAffectedTablesCache
);
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, namedMapProviderCache.invalidate.bind(namedMapProviderCache));
});
var authApi = new AuthApi(pgConnection, metadataBackend, mapStore, templateMaps);
var TablesExtentApi = require('./api/tables_extent_api');
var tablesExtentApi = new TablesExtentApi(pgQueryRunner);
var versions = getAndValidateVersions(serverOptions);
/*******************************************************************************************************************
* Routing
******************************************************************************************************************/
new controller.Layergroup(
pgConnection,
mapStore,
tileBackend,
previewBackend,
attributesBackend,
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
analysisBackend,
authApi
).register(app);
new controller.Map(
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
mapConfigAdapter,
statsBackend,
authApi
).register(app);
new controller.NamedMaps(
namedMapProviderCache,
tileBackend,
previewBackend,
surrogateKeysCache,
tablesExtentApi,
metadataBackend,
pgConnection,
authApi,
userLimitsApi
).register(app);
new controller.NamedMapsAdmin(authApi, templateMaps, userLimitsApi).register(app);
new controller.Analyses(pgConnection, authApi, userLimitsApi).register(app);
new controller.ServerInfo(versions).register(app);
/*******************************************************************************************************************
* END Routing
******************************************************************************************************************/
app.use(errorMiddleware());
const serverInfoController = new ServerInfoController(versions);
serverInfoController.register(app);
return app;
};
@ -278,6 +44,22 @@ function validateOptions(opts) {
}
}
function bootstrapFonts(opts) {
// Set carto renderer configuration for MMLStore
opts.grainstore.carto_env = opts.grainstore.carto_env || {};
var cenv = opts.grainstore.carto_env;
cenv.validation_data = cenv.validation_data || {};
if ( ! cenv.validation_data.fonts ) {
mapnik.register_system_fonts();
mapnik.register_default_fonts();
cenv.validation_data.fonts = _.keys(mapnik.fontFiles());
}
}
function mapnikVersion(opts) {
return opts.grainstore.mapnik_version || mapnik.versions.mapnik;
}
function getAndValidateVersions(options) {
// jshint undef:false
var warn = console.warn.bind(console);
@ -309,127 +91,10 @@ function getAndValidateVersions(options) {
});
// Be nice and warn if configured mapnik version is != installed mapnik version
if (mapnik.versions.mapnik !== options.grainstore.mapnik_version) {
warn('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
if (windshaft.mapnik.versions.mapnik !== options.grainstore.mapnik_version) {
warn('WARNING: detected mapnik version (' + windshaft.mapnik.versions.mapnik + ')' +
' != configured mapnik version (' + options.grainstore.mapnik_version + ')');
}
return installedDependenciesVersions;
}
function bootstrapFonts(opts) {
// Set carto renderer configuration for MMLStore
opts.grainstore.carto_env = opts.grainstore.carto_env || {};
var cenv = opts.grainstore.carto_env;
cenv.validation_data = cenv.validation_data || {};
if ( ! cenv.validation_data.fonts ) {
mapnik.register_system_fonts();
mapnik.register_default_fonts();
cenv.validation_data.fonts = _.keys(mapnik.fontFiles());
}
}
function bootstrap(opts) {
var app;
if (_.isObject(opts.https)) {
// use https if possible
app = express.createServer(opts.https);
} else {
// fall back to http by default
app = express();
}
app.enable('jsonp callback');
app.disable('x-powered-by');
app.disable('etag');
// Fix: https://github.com/CartoDB/Windshaft-cartodb/issues/705
// See: http://expressjs.com/en/4x/api.html#app.set
app.set('json replacer', function (key, value) {
if (value !== value) {
return 'NaN';
}
if (value === Infinity) {
return 'Infinity';
}
if (value === -Infinity) {
return '-Infinity';
}
return value;
});
app.use(bodyParser.json());
app.use(function bootstrap$prepareRequestResponse(req, res, next) {
if (global.environment && global.environment.api_hostname) {
res.set('X-Served-By-Host', global.environment.api_hostname);
}
next();
});
app.use(stats({
enabled: opts.useProfiler,
statsClient: global.statsClient
}));
app.use(lzmaMiddleware());
// temporary measure until we upgrade to newer version expressjs so we can check err.status
app.use(function(err, req, res, next) {
if (err) {
if (err.name === 'SyntaxError') {
res.status(400).json({ errors: [err.name + ': ' + err.message] });
} else {
next(err);
}
} else {
next();
}
});
setupLogger(app, opts);
return app;
}
function setupLogger(app, opts) {
if (global.log4js && opts.log_format) {
var loggerOpts = {
// Allowing for unbuffered logging is mainly
// used to avoid hanging during unit testing.
// TODO: provide an explicit teardown function instead,
// releasing any event handler or timer set by
// this component.
buffer: !opts.unbuffered_logging,
// optional log format
format: opts.log_format
};
app.use(global.log4js.connectLogger(global.log4js.getLogger(), _.defaults(loggerOpts, {level: 'info'})));
}
}
function surrogateKeysCacheBackends(serverOptions) {
var cacheBackends = [];
if (serverOptions.varnish_purge_enabled) {
cacheBackends.push(
new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port)
);
}
if (serverOptions.fastly &&
!!serverOptions.fastly.enabled && !!serverOptions.fastly.apiKey && !!serverOptions.fastly.serviceId) {
cacheBackends.push(
new FastlyCacheBackend(serverOptions.fastly.apiKey, serverOptions.fastly.serviceId)
);
}
return cacheBackends;
}
function mapnikVersion(opts) {
return opts.grainstore.mapnik_version || mapnik.versions.mapnik;
}

View File

@ -49,12 +49,14 @@ module.exports = {
port: global.environment.port,
host: global.environment.host
},
// FIXME: Remove it. This is no longer needed, paths are defined in routers
// This is for inline maps and table maps
base_url: global.environment.base_url_legacy || '/tiles/:table',
/// @deprecated with Windshaft-0.17.0
///base_url_notable: '/tiles',
// FIXME: Remove it. This is no longer needed, paths are defined in routers
// This is for Detached maps
//
// "maps" is the official, while
@ -62,8 +64,55 @@ module.exports = {
//
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
// FIXME: Remove it. This is no longer needed, paths are defined in routers
base_url_templated: global.environment.base_url_templated || '(?:/maps/named|/tiles/template)',
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
routes: global.environment.routes || {
v1: {
paths: [
'/api/v1',
'/user/:user/api/v1',
],
// Base url for the Detached Maps API
// "/api/v1/map" is the new API,
map: {
paths: [
'/map',
]
},
// Base url for the Templated Maps API
// "/api/v1/map/named" is the new API,
template: {
paths: [
'/map/named'
]
}
},
// For compatibility with versions up to 1.6.x
v0: {
paths: [
'/tiles'
],
// Base url for the Detached Maps API
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
map: {
paths: [
'/layergroup'
]
},
// Base url for the Templated Maps API
// "/tiles/template" is for compatibility with versions up to 1.6.x
template: {
paths: [
'/template'
]
}
}
},
grainstore: {
map: {
// TODO: allow to specify in configuration

View File

@ -0,0 +1,19 @@
module.exports = function jsonReplacerFactory () {
// Fix: https://github.com/CartoDB/Windshaft-cartodb/issues/705
// See: http://expressjs.com/en/4x/api.html#app.set
return function jsonReplacer (key, value) {
if (value !== value) {
return 'NaN';
}
if (value === Infinity) {
return 'Infinity';
}
if (value === -Infinity) {
return '-Infinity';
}
return value;
};
};

View File

@ -65,10 +65,8 @@
"preinstall": "make pre-install",
"test": "make test-all",
"update-internal-deps": "rm -rf node_modules && rm -f yarn.lock && yarn",
"docker-install": "sudo apt install docker.io && sudo usermod -aG docker $(whoami)",
"docker-pull": "docker pull cartoimages/windshaft-testing",
"docker-test": "docker run -v `pwd`:/srv cartoimages/windshaft-testing bash docker-test.sh && docker ps --filter status=dead --filter status=exited -aq | xargs -r docker rm -v",
"docker-bash": "docker run -it -v `pwd`:/srv cartoimages/windshaft-testing bash"
"docker-test": "docker run -v `pwd`:/srv carto/nodejs6-xenial-pg101 bash run_tests_docker.sh && docker ps --filter status=dead --filter status=exited -aq | xargs -r docker rm -v",
"docker-bash": "docker run -it -v `pwd`:/srv carto/nodejs6-xenial-pg101 bash"
},
"engines": {
"node": ">=6.9",

9
run_tests_docker.sh Normal file
View File

@ -0,0 +1,9 @@
# start PostgreSQL
/etc/init.d/postgresql start
# install dependencies
npm install -g yarn@0.27.5
yarn
# run tests
POSTGIS_VERSION=2.4 npm test

View File

@ -234,7 +234,7 @@ describe('tests from old api translated to multilayer', function() {
var parsed = JSON.parse(res.body);
assert.ok(parsed.errors);
assert.equal(parsed.errors.length, 1);
assert.ok(parsed.errors[0].match(/^Unexpected token W/));
assert.ok(parsed.errors[0].match(/Unexpected token W/));
done();
}
@ -373,6 +373,7 @@ describe('tests from old api translated to multilayer', function() {
};
// reset internal cacheChannel cache
// FIXME: we need a better way to reset cache while running tests
server.layergroupAffectedTablesCache.cache.reset();
assert.response(server,

View File

@ -7,8 +7,35 @@ var overviewsQueryRewriter = new OverviewsQueryRewriter({
});
module.exports = _.extend({}, serverOptions, {
// FIXME: Remove it. This is no longer needed, paths are defined in routers
base_url: '/database/:dbname/table/:table',
// FIXME: Remove it. This is no longer needed, paths are defined in routers
base_url_mapconfig: '/database/:dbname/layergroup',
routes: {
v0: {
paths: [
'/tiles',
'/database/:dbname'
],
// Base url for the Detached Maps API
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
map: {
paths: [
'/layergroup'
]
},
// Base url for the Templated Maps API
// "/tiles/template" is for compatibility with versions up to 1.6.x
template: {
paths: [
'/template'
]
}
}
},
grainstore: {
datasource: {
geometry_field: 'the_geom',

View File

@ -5,8 +5,8 @@ const redis = require('redis');
const RedisPool = require('redis-mpool');
const cartodbRedis = require('cartodb-redis');
const TestClient = require('../support/test-client');
const UserLimitsApi = require('../../lib/cartodb/api/user_limits_api');
const rateLimitMiddleware = require('../../lib/cartodb/middleware/rate-limit');
const UserLimitsBackend = require('../../lib/cartodb/backends/user-limits');
const rateLimitMiddleware = require('../../lib/cartodb/api/middlewares/rate-limit');
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimitMiddleware;
let userLimitsApi;
@ -218,7 +218,7 @@ describe('rate limit middleware', function () {
const redisPool = new RedisPool(global.environment.redis);
const metadataBackend = cartodbRedis({ pool: redisPool });
userLimitsApi = new UserLimitsApi(metadataBackend, {
userLimitsApi = new UserLimitsBackend(metadataBackend, {
limits: {
rateLimitsEnabled: global.environment.enabledFeatures.rateLimitsEnabled
}

View File

@ -29,6 +29,7 @@ describe('template_api', function() {
before(function () {
server = new CartodbWindshaft(serverOptions);
server.setMaxListeners(0);
// FIXME: we need a better way to reset cache while running tests
server.layergroupAffectedTablesCache.cache.reset();
});
@ -1060,6 +1061,7 @@ describe('template_api', function() {
assert.ok(cc);
assert.equal(cc, expectedCC);
// hack simulating restart...
// FIXME: we need a better way to reset cache while running tests
server.layergroupAffectedTablesCache.cache.reset(); // need to clean channel cache
var get_request = {
url: '/api/v1/map/' + layergroupid + ':cb1/0/0/0/1.json.torque?auth_token=valid1',

View File

@ -5,18 +5,18 @@ var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var PgConnection = require(__dirname + '/../../lib/cartodb/backends/pg_connection');
var PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner');
var OverviewsMetadataApi = require('../../lib/cartodb/api/overviews_metadata_api');
var FilterStatsApi = require('../../lib/cartodb/api/filter_stats_api');
var OverviewsMetadataBackend = require('../../lib/cartodb/backends/overviews-metadata');
var FilterStatsBackend = require('../../lib/cartodb/backends/filter-stats');
var MapConfigOverviewsAdapter = require('../../lib/cartodb/models/mapconfig/adapter/mapconfig-overviews-adapter');
var redisPool = new RedisPool(global.environment.redis);
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var pgQueryRunner = new PgQueryRunner(pgConnection);
var overviewsMetadataApi = new OverviewsMetadataApi(pgQueryRunner);
var filterStatsApi = new FilterStatsApi(pgQueryRunner);
var overviewsMetadataBackend = new OverviewsMetadataBackend(pgQueryRunner);
var filterStatsBackend = new FilterStatsBackend(pgQueryRunner);
var mapConfigOverviewsAdapter = new MapConfigOverviewsAdapter(overviewsMetadataApi, filterStatsApi);
var mapConfigOverviewsAdapter = new MapConfigOverviewsAdapter(overviewsMetadataBackend, filterStatsBackend);
describe('MapConfigOverviewsAdapter', function() {

View File

@ -7,24 +7,24 @@ var cartodbRedis = require('cartodb-redis');
var PgConnection = require('../../lib/cartodb/backends/pg_connection');
var PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner');
var OverviewsMetadataApi = require('../../lib/cartodb/api/overviews_metadata_api');
var OverviewsMetadataBackend = require('../../lib/cartodb/backends/overviews-metadata');
describe('OverviewsMetadataApi', function() {
describe('OverviewsMetadataBackend', function() {
var overviewsMetadataApi;
var overviewsMetadataBackend;
before(function() {
var redisPool = new RedisPool(global.environment.redis);
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var pgQueryRunner = new PgQueryRunner(pgConnection);
overviewsMetadataApi = new OverviewsMetadataApi(pgQueryRunner);
overviewsMetadataBackend = new OverviewsMetadataBackend(pgQueryRunner);
});
it('should return an empty relation for tables that have no overviews', function(done) {
var query = 'select * from test_table';
overviewsMetadataApi.getOverviewsMetadata('localhost', query, function(err, result) {
overviewsMetadataBackend.getOverviewsMetadata('localhost', query, function(err, result) {
assert.ok(!err, err);
assert.deepEqual(result, {});
@ -35,7 +35,7 @@ describe('OverviewsMetadataApi', function() {
it('should return overviews metadata', function(done) {
var query = 'select * from test_table_overviews';
overviewsMetadataApi.getOverviewsMetadata('localhost', query, function(err, result) {
overviewsMetadataBackend.getOverviewsMetadata('localhost', query, function(err, result) {
assert.ok(!err, err);
assert.deepEqual(result, {

View File

@ -1,7 +1,7 @@
require('../../support/test_helper.js');
var assert = require('assert');
var errorMiddleware = require('../../../lib/cartodb/middleware/error-middleware');
var errorMiddleware = require('../../../lib/cartodb/api/middlewares/error-middleware');
describe('error-middleware', function() {

View File

@ -2,7 +2,7 @@ require('../../support/test_helper');
var assert = require('assert');
var errorMiddleware = require('../../../lib/cartodb/middleware/error-middleware');
var errorMiddleware = require('../../../lib/cartodb/api/middlewares/error-middleware');
describe('error messages clean up', function() {

View File

@ -1,7 +1,7 @@
var assert = require('assert');
var testHelper = require('../../support/test_helper');
var lzmaMiddleware = require('../../../lib/cartodb/middleware/lzma');
var lzmaMiddleware = require('../../../lib/cartodb/api/middlewares/lzma');
describe('lzma-middleware', function() {

View File

@ -1,5 +1,5 @@
const assert = require('assert');
const coordinates = require('../../../../lib/cartodb/middleware/coordinates');
const coordinates = require('../../../../lib/cartodb/api/middlewares/coordinates');
describe('coordinates middleware', function () {
it('should return error: invalid zoom paramenter (-1)', function (done) {

View File

@ -1,6 +1,5 @@
require('../../../support/test_helper.js');
var _ = require('underscore');
var assert = require('assert');
var cartodbServer = require('../../../../lib/cartodb/server');
var serverOptions = require('../../../../lib/cartodb/server_options');
@ -30,15 +29,7 @@ describe('windshaft', function() {
function(){
var ws = cartodbServer({unbuffered_logging:true});
ws.listen();
}, /Cannot read property 'mapnik' of undefined/
}, /Must initialise server with/
);
});
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');
});
});

View File

@ -4,13 +4,13 @@ var _ = require('underscore');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var PgConnection = require('../../../lib/cartodb/backends/pg_connection');
var AuthApi = require('../../../lib/cartodb/api/auth_api');
var AuthBackend = require('../../../lib/cartodb/backends/auth');
var TemplateMaps = require('../../../lib/cartodb/backends/template_maps');
const cleanUpQueryParamsMiddleware = require('../../../lib/cartodb/middleware/clean-up-query-params');
const authorizeMiddleware = require('../../../lib/cartodb/middleware/authorize');
const dbConnSetupMiddleware = require('../../../lib/cartodb/middleware/db-conn-setup');
const credentialsMiddleware = require('../../../lib/cartodb/middleware/credentials');
const cleanUpQueryParamsMiddleware = require('../../../lib/cartodb/api/middlewares/clean-up-query-params');
const authorizeMiddleware = require('../../../lib/cartodb/api/middlewares/authorize');
const dbConnSetupMiddleware = require('../../../lib/cartodb/api/middlewares/db-conn-setup');
const credentialsMiddleware = require('../../../lib/cartodb/api/middlewares/credentials');
var windshaft = require('windshaft');
@ -31,10 +31,10 @@ describe('prepare-context', function() {
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var templateMaps = new TemplateMaps(redisPool);
var authApi = new AuthApi(pgConnection, metadataBackend, mapStore, templateMaps);
var authBackend = new AuthBackend(pgConnection, metadataBackend, mapStore, templateMaps);
cleanUpQueryParams = cleanUpQueryParamsMiddleware();
authorize = authorizeMiddleware(authApi);
authorize = authorizeMiddleware(authBackend);
dbConnSetup = dbConnSetupMiddleware(pgConnection);
setCredentials = credentialsMiddleware();
});

View File

@ -11,7 +11,7 @@
node-pre-gyp "0.7.0"
protozero "1.5.1"
"@carto/tilelive-bridge@github:cartodb/tilelive-bridge#2.5.1-cdb7":
"@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"
dependencies:
@ -23,7 +23,7 @@
version "1.0.5"
resolved "https://registry.yarnpkg.com/@mapbox/sphericalmercator/-/sphericalmercator-1.0.5.tgz#70237b9774095ed1cfdbcea7a8fd1fc82b2691f2"
"abaculus@github:cartodb/abaculus#2.0.3-cdb8":
abaculus@cartodb/abaculus#2.0.3-cdb8:
version "2.0.3-cdb8"
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/31c03f2442943d4c47740fa154cda753b5cccd8a"
dependencies:
@ -249,7 +249,7 @@ camshaft@0.61.8:
dot "^1.0.3"
request "2.85.0"
"canvas@github:cartodb/node-canvas#1.6.2-cdb2":
canvas@cartodb/node-canvas#1.6.2-cdb2:
version "1.6.2-cdb2"
resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766"
dependencies:
@ -275,7 +275,7 @@ carto@CartoDB/carto#0.15.1-cdb1:
optimist "~0.6.0"
underscore "~1.6.0"
"carto@github:cartodb/carto#0.15.1-cdb3":
carto@cartodb/carto#0.15.1-cdb3:
version "0.15.1-cdb3"
resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7"
dependencies:
@ -1292,7 +1292,7 @@ mapnik-reference@~8.5.3:
dependencies:
semver "^5.1.0"
"mapnik-vector-tile@github:cartodb/mapnik-vector-tile#v1.6.1-carto.1":
mapnik-vector-tile@cartodb/mapnik-vector-tile#v1.6.1-carto.1:
version "1.6.1-carto.1"
resolved "https://codeload.github.com/cartodb/mapnik-vector-tile/tar.gz/0111f7117946179d62ec7a6eba2f4e9fb355d05e"
@ -2258,7 +2258,7 @@ through@2:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb12":
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"
dependencies: