Merge branch 'master' into fix-dataview-typeof
This commit is contained in:
commit
81bace1dca
8
NEWS.md
8
NEWS.md
@ -5,12 +5,14 @@ Released 2018-mm-dd
|
||||
|
||||
New features:
|
||||
- CI tests with Ubuntu Xenial + PostgreSQL 10.1 and Ubuntu Precise + PostgreSQL 9.5
|
||||
|
||||
- Upgrades Windshaft to [4.7.3](https://github.com/CartoDB/Windshaft/blob/4.7.3/NEWS.md#version-473) which includes:
|
||||
- A fix in mapnik-vector-tile to avoid grouping together properties with the same value but a different type.
|
||||
- Performance improvements in the marker symbolizer (local cache, avoid building the collision matrix when possible).
|
||||
- MVT: Disable simplify_distance to avoid multiple simplifications.
|
||||
- Fix a bug with zero length lines not being rendered when using the marker symbolizer.
|
||||
|
||||
Bug Fixes:
|
||||
- Validates tile coordinates (z/x/y) from request params to be a valid integer value.
|
||||
- Upgrades Windshaft to 4.7.1, which includes a fix in mapnik-vector-tile to avoid grouping together properties with the same value but different type.
|
||||
|
||||
|
||||
## 6.1.0
|
||||
Released 2018-04-16
|
||||
|
2
app.js
2
app.js
@ -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');
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
319
lib/cartodb/api/api-router.js
Normal file
319
lib/cartodb/api/api-router.js
Normal 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({ mergeParams: true });
|
||||
|
||||
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;
|
||||
}
|
@ -1,44 +1,39 @@
|
||||
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) {
|
||||
module.exports = class AnalysesController {
|
||||
constructor (pgConnection, authBackend, userLimitsBackend) {
|
||||
this.pgConnection = pgConnection;
|
||||
this.authApi = authApi;
|
||||
this.userLimitsApi = userLimitsApi;
|
||||
}
|
||||
this.authBackend = authBackend;
|
||||
this.userLimitsBackend = userLimitsBackend;
|
||||
}
|
||||
|
||||
module.exports = AnalysesController;
|
||||
register (mapRouter) {
|
||||
mapRouter.get('/analyses/catalog', this.middlewares());
|
||||
}
|
||||
|
||||
AnalysesController.prototype.register = function (app) {
|
||||
const { base_url_mapconfig: mapconfigBasePath } = app;
|
||||
|
||||
app.get(
|
||||
`${mapconfigBasePath}/analyses/catalog`,
|
||||
cors(),
|
||||
user(),
|
||||
middlewares () {
|
||||
return [
|
||||
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()
|
||||
);
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
function createPGClient () {
|
||||
@ -107,6 +102,7 @@ function prepareResponse () {
|
||||
return -1;
|
||||
});
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = { catalog: analysisCatalog };
|
||||
|
||||
next();
|
59
lib/cartodb/api/map/analysis-layergroup-controller.js
Normal file
59
lib/cartodb/api/map/analysis-layergroup-controller.js
Normal file
@ -0,0 +1,59 @@
|
||||
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', this.middlewares());
|
||||
}
|
||||
|
||||
middlewares () {
|
||||
return [
|
||||
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();
|
||||
});
|
||||
};
|
||||
}
|
212
lib/cartodb/api/map/anonymous-map-controller.js
Normal file
212
lib/cartodb/api/map/anonymous-map-controller.js
Normal 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;
|
||||
|
||||
module.exports = class AnonymousMapController {
|
||||
/**
|
||||
* @param {AuthBackend} authBackend
|
||||
* @param {PgConnection} pgConnection
|
||||
* @param {TemplateMaps} templateMaps
|
||||
* @param {MapBackend} mapBackend
|
||||
* @param metadataBackend
|
||||
* @param {SurrogateKeysCache} surrogateKeysCache
|
||||
* @param {UserLimitsBackend} userLimitsBackend
|
||||
* @param {LayergroupAffectedTables} layergroupAffectedTables
|
||||
* @param {MapConfigAdapter} mapConfigAdapter
|
||||
* @param {StatsBackend} statsBackend
|
||||
* @constructor
|
||||
*/
|
||||
constructor (
|
||||
pgConnection,
|
||||
templateMaps,
|
||||
mapBackend,
|
||||
metadataBackend,
|
||||
surrogateKeysCache,
|
||||
userLimitsBackend,
|
||||
layergroupAffectedTables,
|
||||
mapConfigAdapter,
|
||||
statsBackend,
|
||||
authBackend,
|
||||
layergroupMetadata
|
||||
) {
|
||||
this.pgConnection = pgConnection;
|
||||
this.templateMaps = templateMaps;
|
||||
this.mapBackend = mapBackend;
|
||||
this.metadataBackend = metadataBackend;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.userLimitsBackend = userLimitsBackend;
|
||||
this.layergroupAffectedTables = layergroupAffectedTables;
|
||||
this.mapConfigAdapter = mapConfigAdapter;
|
||||
this.statsBackend = statsBackend;
|
||||
this.authBackend = authBackend;
|
||||
this.layergroupMetadata = layergroupMetadata;
|
||||
}
|
||||
|
||||
register (mapRouter) {
|
||||
mapRouter.options('/');
|
||||
mapRouter.get('/', this.middlewares());
|
||||
mapRouter.post('/', this.middlewares());
|
||||
}
|
||||
|
||||
middlewares () {
|
||||
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();
|
||||
});
|
||||
};
|
||||
}
|
@ -1,54 +1,50 @@
|
||||
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;
|
||||
register (mapRouter) {
|
||||
mapRouter.get('/:token/:layer/attributes/:fid', this.middlewares());
|
||||
}
|
||||
|
||||
app.get(
|
||||
`${mapConfigBasePath}/:token/:layer/attributes/:fid`,
|
||||
cors(),
|
||||
user(),
|
||||
middlewares () {
|
||||
return [
|
||||
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,9 +52,8 @@ module.exports = class AttribitesController {
|
||||
cacheControlHeader(),
|
||||
cacheChannelHeader(),
|
||||
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
lastModifiedHeader(),
|
||||
sendResponse()
|
||||
);
|
||||
lastModifiedHeader()
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
@ -85,6 +80,7 @@ function getFeatureAttributes (attributesBackend) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = tile;
|
||||
|
||||
next();
|
142
lib/cartodb/api/map/dataview-layergroup-controller.js
Normal file
142
lib/cartodb/api/map/dataview-layergroup-controller.js
Normal file
@ -0,0 +1,142 @@
|
||||
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('../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
|
||||
'own_filter', // 0, 1
|
||||
'no_filters', // 0, 1
|
||||
'bbox', // w,s,e,n
|
||||
'start', // number
|
||||
'end', // number
|
||||
'column_type', // string
|
||||
'bins', // number
|
||||
'aggregation', //string
|
||||
'offset', // number
|
||||
'q', // widgets search
|
||||
'categories', // number
|
||||
];
|
||||
|
||||
module.exports = class DataviewLayergroupController {
|
||||
constructor (
|
||||
dataviewBackend,
|
||||
pgConnection,
|
||||
mapStore,
|
||||
userLimitsBackend,
|
||||
layergroupAffectedTablesCache,
|
||||
authBackend,
|
||||
surrogateKeysCache
|
||||
) {
|
||||
this.dataviewBackend = dataviewBackend;
|
||||
this.pgConnection = pgConnection;
|
||||
this.mapStore = mapStore;
|
||||
this.userLimitsBackend = userLimitsBackend;
|
||||
this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
|
||||
this.authBackend = authBackend;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
}
|
||||
|
||||
register (mapRouter) {
|
||||
// Undocumented/non-supported API endpoint methods.
|
||||
// Use at your own peril.
|
||||
|
||||
mapRouter.get('/:token/dataview/:dataviewName', this.middlewares({
|
||||
action: 'get',
|
||||
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW
|
||||
}));
|
||||
|
||||
mapRouter.get('/:token/:layer/widget/:dataviewName', this.middlewares({
|
||||
action: 'get',
|
||||
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW
|
||||
}));
|
||||
|
||||
mapRouter.get('/:token/dataview/:dataviewName/search', this.middlewares({
|
||||
action: 'search',
|
||||
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH
|
||||
}));
|
||||
|
||||
mapRouter.get('/:token/:layer/widget/:dataviewName/search', this.middlewares({
|
||||
action: 'search',
|
||||
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH
|
||||
}));
|
||||
}
|
||||
|
||||
middlewares ({ action, rateLimitGroup }) {
|
||||
return [
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(this.authBackend),
|
||||
dbConnSetup(this.pgConnection),
|
||||
rateLimit(this.userLimitsBackend, rateLimitGroup),
|
||||
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
|
||||
createMapStoreMapConfigProvider(
|
||||
this.mapStore,
|
||||
this.userLimitsBackend,
|
||||
this.pgConnection,
|
||||
this.layergroupAffectedTablesCache
|
||||
),
|
||||
action === 'search' ? dataviewSearch(this.dataviewBackend) : getDataview(this.dataviewBackend),
|
||||
cacheControlHeader(),
|
||||
cacheChannelHeader(),
|
||||
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
lastModifiedHeader()
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
function getDataview (dataviewBackend) {
|
||||
return function getDataviewMiddleware (req, res, next) {
|
||||
const { user, mapConfigProvider } = res.locals;
|
||||
const { dataviewName } = req.params;
|
||||
const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals;
|
||||
|
||||
const params = Object.assign({ dataviewName, dbuser, dbname, dbpassword, dbhost, dbport }, req.query);
|
||||
|
||||
dataviewBackend.getDataview(mapConfigProvider, user, params, (err, dataview, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'GET DATAVIEW';
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = dataview;
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function dataviewSearch (dataviewBackend) {
|
||||
return function dataviewSearchMiddleware (req, res, next) {
|
||||
const { user, mapConfigProvider } = res.locals;
|
||||
const { dataviewName } = req.params;
|
||||
const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals;
|
||||
|
||||
const params = Object.assign({ dbuser, dbname, dbpassword, dbhost, dbport }, req.query);
|
||||
|
||||
dataviewBackend.search(mapConfigProvider, user, dataviewName, params, (err, searchResult, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'GET DATAVIEW SEARCH';
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = searchResult;
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
129
lib/cartodb/api/map/map-router.js
Normal file
129
lib/cartodb/api/map/map-router.js
Normal 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({ mergeParams: true });
|
||||
|
||||
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));
|
||||
}
|
||||
};
|
@ -1,94 +1,85 @@
|
||||
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 noop = require('../middlewares/noop');
|
||||
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 checkStaticImageFormat = require('../middlewares/check-static-image-format');
|
||||
|
||||
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) {
|
||||
mapRouter.get('/static/center/:token/:z/:lat/:lng/:width/:height.:format', this.middlewares({
|
||||
validateZoom: true,
|
||||
previewType: 'centered'
|
||||
}));
|
||||
|
||||
mapRouter.get('/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format', this.middlewares({
|
||||
validateZoom: false,
|
||||
previewType: 'bbox'
|
||||
}));
|
||||
}
|
||||
|
||||
middlewares ({ validateZoom, previewType }) {
|
||||
const forcedFormat = 'png';
|
||||
|
||||
app.get(
|
||||
`${mapConfigBasePath}/static/center/:token/:z/:lat/:lng/:width/:height.:format`,
|
||||
cors(),
|
||||
user(),
|
||||
layergroupToken(),
|
||||
coordinates({ z: true, x: false, y: false }),
|
||||
credentials(),
|
||||
authorize(this.authApi),
|
||||
dbConnSetup(this.pgConnection),
|
||||
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.STATIC),
|
||||
cleanUpQueryParams(['layer']),
|
||||
createMapStoreMapConfigProvider(
|
||||
this.mapStore,
|
||||
this.userLimitsApi,
|
||||
this.pgConnection,
|
||||
this.layergroupAffectedTablesCache,
|
||||
forcedFormat
|
||||
),
|
||||
getPreviewImageByCenter(this.previewBackend),
|
||||
cacheControlHeader(),
|
||||
cacheChannelHeader(),
|
||||
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
lastModifiedHeader(),
|
||||
sendResponse()
|
||||
);
|
||||
let getPreviewImage;
|
||||
|
||||
app.get(
|
||||
`${mapConfigBasePath}/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`,
|
||||
cors(),
|
||||
user(),
|
||||
if (previewType === 'centered') {
|
||||
getPreviewImage = getPreviewImageByCenter;
|
||||
}
|
||||
|
||||
if (previewType === 'bbox') {
|
||||
getPreviewImage = getPreviewImageByBoundingBox;
|
||||
}
|
||||
|
||||
return [
|
||||
layergroupToken(),
|
||||
validateZoom ? coordinates({ z: true, x: false, y: false }) : noop(),
|
||||
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']),
|
||||
checkStaticImageFormat(),
|
||||
createMapStoreMapConfigProvider(
|
||||
this.mapStore,
|
||||
this.userLimitsApi,
|
||||
this.userLimitsBackend,
|
||||
this.pgConnection,
|
||||
this.layergroupAffectedTablesCache,
|
||||
forcedFormat
|
||||
),
|
||||
getPreviewImageByBoundingBox(this.previewBackend),
|
||||
getPreviewImage(this.previewBackend),
|
||||
cacheControlHeader(),
|
||||
cacheChannelHeader(),
|
||||
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
lastModifiedHeader(),
|
||||
sendResponse()
|
||||
);
|
||||
lastModifiedHeader()
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
@ -120,6 +111,7 @@ function getPreviewImageByCenter (previewBackend) {
|
||||
|
||||
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = image;
|
||||
|
||||
next();
|
||||
@ -155,6 +147,7 @@ function getPreviewImageByBoundingBox (previewBackend) {
|
||||
|
||||
res.set('Content-Type', headers['Content-Type'] || `image/${format}`);
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = image;
|
||||
|
||||
next();
|
@ -1,17 +1,14 @@
|
||||
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 checkStaticImageFormat = require('../middlewares/check-static-image-format');
|
||||
const rateLimit = require('../middlewares/rate-limit');
|
||||
const { RATE_LIMIT_ENDPOINTS_GROUPS } = rateLimit;
|
||||
|
||||
const DEFAULT_ZOOM_CENTER = {
|
||||
@ -26,70 +23,40 @@ function numMapper(n) {
|
||||
return +n;
|
||||
}
|
||||
|
||||
function NamedMapsController (
|
||||
module.exports = class PreviewTemplateController {
|
||||
constructor (
|
||||
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;
|
||||
register (mapRouter) {
|
||||
mapRouter.get('/static/named/:template_id/:width/:height.:format', this.middlewares());
|
||||
}
|
||||
|
||||
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(),
|
||||
middlewares () {
|
||||
return [
|
||||
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({
|
||||
checkStaticImageFormat(),
|
||||
namedMapProvider({
|
||||
namedMapProviderCache: this.namedMapProviderCache,
|
||||
label: 'STATIC_VIZ_MAP', forcedFormat: 'png'
|
||||
}),
|
||||
@ -98,51 +65,18 @@ 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 +143,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 +169,7 @@ function getStaticImageOptions ({ tablesExtentApi }) {
|
||||
return next();
|
||||
}
|
||||
|
||||
tablesExtentApi.getBounds(user, tables, (err, bounds) => {
|
||||
tablesExtentBackend.getBounds(user, tables, (err, bounds) => {
|
||||
if (err) {
|
||||
return next();
|
||||
}
|
||||
@ -365,6 +273,7 @@ function getImage({ previewBackend, label }) {
|
||||
res.set(headers);
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = image;
|
||||
|
||||
next();
|
||||
@ -384,6 +293,7 @@ function getImage({ previewBackend, label }) {
|
||||
res.set(headers);
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = image;
|
||||
|
||||
next();
|
170
lib/cartodb/api/map/tile-layergroup-controller.js
Normal file
170
lib/cartodb/api/map/tile-layergroup-controller.js
Normal file
@ -0,0 +1,170 @@
|
||||
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})([^\/]+?)`;
|
||||
|
||||
// Sadly the path that matches 1 also matches with 2 so we need to tell to express
|
||||
// that performs only the middlewares of the first path that matches
|
||||
// for that we use one array to group all paths.
|
||||
mapRouter.get([
|
||||
`/:token/:z/:x/:y@:scale_factor?x.:format`, // 1
|
||||
`/:token/:z/:x/:y.:format`, // 2
|
||||
`/:token${not('static')}/:layer/:z/:x/:y.(:format)`
|
||||
], this.middlewares());
|
||||
}
|
||||
|
||||
middlewares () {
|
||||
return [
|
||||
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);
|
||||
};
|
||||
}
|
14
lib/cartodb/api/middlewares/augment-layergroup-data.js
Normal file
14
lib/cartodb/api/middlewares/augment-layergroup-data.js
Normal 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();
|
||||
};
|
||||
};
|
@ -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) {
|
11
lib/cartodb/api/middlewares/check-json-content-type.js
Normal file
11
lib/cartodb/api/middlewares/check-json-content-type.js
Normal 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();
|
||||
};
|
||||
};
|
11
lib/cartodb/api/middlewares/check-static-image-format.js
Normal file
11
lib/cartodb/api/middlewares/check-static-image-format.js
Normal file
@ -0,0 +1,11 @@
|
||||
const VALID_IMAGE_FORMATS = ['png', 'jpg'];
|
||||
|
||||
module.exports = function checkStaticImageFormat () {
|
||||
return function checkStaticImageFormatMiddleware (req, res, next) {
|
||||
if(!VALID_IMAGE_FORMATS.includes(req.params.format)) {
|
||||
return next(new Error(`Unsupported image format "${req.params.format}"`));
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
18
lib/cartodb/api/middlewares/cors.js
Normal file
18
lib/cartodb/api/middlewares/cors.js
Normal 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();
|
||||
};
|
||||
};
|
16
lib/cartodb/api/middlewares/increment-map-view-count.js
Normal file
16
lib/cartodb/api/middlewares/increment-map-view-count.js
Normal 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();
|
||||
});
|
||||
};
|
||||
};
|
9
lib/cartodb/api/middlewares/init-profiler.js
Normal file
9
lib/cartodb/api/middlewares/init-profiler.js
Normal 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();
|
||||
};
|
||||
};
|
9
lib/cartodb/api/middlewares/initialize-status-code.js
Normal file
9
lib/cartodb/api/middlewares/initialize-status-code.js
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = function initializeStatusCode () {
|
||||
return function initializeStatusCodeMiddleware (req, res, next) {
|
||||
if (req.method !== 'OPTIONS') {
|
||||
res.statusCode = 404;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
39
lib/cartodb/api/middlewares/last-updated-time-layergroup.js
Normal file
39
lib/cartodb/api/middlewares/last-updated-time-layergroup.js
Normal 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);
|
||||
}
|
26
lib/cartodb/api/middlewares/layer-stats.js
Normal file
26
lib/cartodb/api/middlewares/layer-stats.js
Normal 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();
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
15
lib/cartodb/api/middlewares/layergroup-id-header.js
Normal file
15
lib/cartodb/api/middlewares/layergroup-id-header.js
Normal 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();
|
||||
};
|
||||
};
|
14
lib/cartodb/api/middlewares/layergroup-metadata.js
Normal file
14
lib/cartodb/api/middlewares/layergroup-metadata.js
Normal 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();
|
||||
};
|
||||
};
|
@ -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}"`;
|
||||
};
|
22
lib/cartodb/api/middlewares/logger.js
Normal file
22
lib/cartodb/api/middlewares/logger.js
Normal 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);
|
||||
};
|
35
lib/cartodb/api/middlewares/map-error.js
Normal file
35
lib/cartodb/api/middlewares/map-error.js
Normal 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;
|
||||
}
|
@ -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
|
32
lib/cartodb/api/middlewares/named-map-provider.js
Normal file
32
lib/cartodb/api/middlewares/named-map-provider.js
Normal 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();
|
||||
});
|
||||
};
|
||||
};
|
5
lib/cartodb/api/middlewares/noop.js
Normal file
5
lib/cartodb/api/middlewares/noop.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = function noop () {
|
||||
return function noopMiddleware (req, res, next) {
|
||||
next();
|
||||
};
|
||||
};
|
@ -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);
|
||||
}
|
@ -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);
|
11
lib/cartodb/api/middlewares/served-by-host-header.js
Normal file
11
lib/cartodb/api/middlewares/served-by-host-header.js
Normal 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();
|
||||
};
|
||||
};
|
@ -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');
|
||||
|
@ -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) {
|
10
lib/cartodb/api/middlewares/syntax-error.js
Normal file
10
lib/cartodb/api/middlewares/syntax-error.js
Normal 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);
|
||||
};
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
const CdbRequest = require('../models/cdb_request');
|
||||
const CdbRequest = require('../../models/cdb_request');
|
||||
|
||||
module.exports = function user () {
|
||||
const cdbRequest = new CdbRequest();
|
@ -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) {
|
@ -1,95 +1,92 @@
|
||||
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
|
||||
module.exports = class AdminTemplateController {
|
||||
/**
|
||||
* @param {AuthBackend} authBackend
|
||||
* @param {PgConnection} pgConnection
|
||||
* @param {TemplateMaps} templateMaps
|
||||
* @constructor
|
||||
*/
|
||||
function NamedMapsAdminController(authApi, templateMaps, userLimitsApi) {
|
||||
this.authApi = authApi;
|
||||
constructor (authBackend, templateMaps, userLimitsBackend) {
|
||||
this.authBackend = authBackend;
|
||||
this.templateMaps = templateMaps;
|
||||
this.userLimitsApi = userLimitsApi;
|
||||
}
|
||||
this.userLimitsBackend = userLimitsBackend;
|
||||
}
|
||||
|
||||
module.exports = NamedMapsAdminController;
|
||||
register (templateRouter) {
|
||||
templateRouter.options(`/:template_id`);
|
||||
|
||||
NamedMapsAdminController.prototype.register = function (app) {
|
||||
const { base_url_templated: templateBasePath } = app;
|
||||
templateRouter.post('/', this.middlewares({
|
||||
action: 'create',
|
||||
label: 'POST TEMPLATE',
|
||||
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_CREATE
|
||||
}));
|
||||
|
||||
app.post(
|
||||
`${templateBasePath}/`,
|
||||
cors(),
|
||||
user(),
|
||||
templateRouter.put('/:template_id', this.middlewares({
|
||||
action: 'update',
|
||||
label: 'PUT TEMPLATE',
|
||||
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_UPDATE
|
||||
}));
|
||||
|
||||
templateRouter.get('/:template_id', this.middlewares({
|
||||
action: 'get',
|
||||
label: 'GET TEMPLATE',
|
||||
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_GET
|
||||
}));
|
||||
|
||||
templateRouter.delete('/:template_id', this.middlewares({
|
||||
action: 'delete',
|
||||
label: 'DELETE TEMPLATE',
|
||||
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_DELETE
|
||||
}));
|
||||
|
||||
templateRouter.get('/', this.middlewares({
|
||||
action: 'list',
|
||||
label: 'GET TEMPLATE LIST',
|
||||
rateLimitGroup: RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_LIST
|
||||
}));
|
||||
}
|
||||
|
||||
middlewares ({ action, label, rateLimitGroup }) {
|
||||
let template;
|
||||
|
||||
if (action === 'create') {
|
||||
template = createTemplate;
|
||||
}
|
||||
|
||||
if (action === 'update') {
|
||||
template = updateTemplate;
|
||||
}
|
||||
|
||||
if (action === 'get') {
|
||||
template = retrieveTemplate;
|
||||
}
|
||||
|
||||
if (action === 'delete') {
|
||||
template = destroyTemplate;
|
||||
}
|
||||
|
||||
if (action === 'list') {
|
||||
template = listTemplates;
|
||||
}
|
||||
|
||||
return [
|
||||
credentials(),
|
||||
authorizedByAPIKey({ authBackend: this.authBackend, action, label }),
|
||||
rateLimit(this.userLimitsBackend, rateLimitGroup),
|
||||
checkContentType({ action: 'POST', label: 'POST TEMPLATE' }),
|
||||
authorizedByAPIKey({ authApi: this.authApi, action: 'create', label: 'POST TEMPLATE' }),
|
||||
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_CREATE),
|
||||
createTemplate({ templateMaps: this.templateMaps }),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.put(
|
||||
`${templateBasePath}/:template_id`,
|
||||
cors(),
|
||||
user(),
|
||||
credentials(),
|
||||
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()
|
||||
);
|
||||
|
||||
app.get(
|
||||
`${templateBasePath}/:template_id`,
|
||||
cors(),
|
||||
user(),
|
||||
credentials(),
|
||||
authorizedByAPIKey({ authApi: this.authApi, action: 'get', label: 'GET TEMPLATE' }),
|
||||
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_GET),
|
||||
retrieveTemplate({ templateMaps: this.templateMaps }),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.delete(
|
||||
`${templateBasePath}/:template_id`,
|
||||
cors(),
|
||||
user(),
|
||||
credentials(),
|
||||
authorizedByAPIKey({ authApi: this.authApi, action: 'delete', label: 'DELETE TEMPLATE' }),
|
||||
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_DELETE),
|
||||
destroyTemplate({ templateMaps: this.templateMaps }),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
`${templateBasePath}/`,
|
||||
cors(),
|
||||
user(),
|
||||
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')
|
||||
);
|
||||
template({ templateMaps: this.templateMaps })
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
function checkContentType ({ action, label }) {
|
||||
function checkContentType ({ label }) {
|
||||
return function checkContentTypeMiddleware (req, res, next) {
|
||||
if (!req.is('application/json')) {
|
||||
const error = new Error(`template ${action} data must be of type application/json`);
|
||||
if ((req.method === 'POST' || req.method === 'PUT') && !req.is('application/json')) {
|
||||
const error = new Error(`${req.method} template data must be of type application/json`);
|
||||
error.label = label;
|
||||
return next(error);
|
||||
}
|
||||
@ -98,11 +95,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 +135,7 @@ function createTemplate ({ templateMaps }) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = { template_id: templateId };
|
||||
|
||||
next();
|
||||
@ -156,6 +154,7 @@ function updateTemplate ({ templateMaps }) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = { template_id: templateId };
|
||||
|
||||
next();
|
||||
@ -184,6 +183,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 +222,7 @@ function listTemplates ({ templateMaps }) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.body = { template_ids: templateIds };
|
||||
|
||||
next();
|
213
lib/cartodb/api/template/named-template-controller.js
Normal file
213
lib/cartodb/api/template/named-template-controller.js
Normal file
@ -0,0 +1,213 @@
|
||||
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;
|
||||
|
||||
module.exports = class NamedMapController {
|
||||
/**
|
||||
* @param {PgConnection} pgConnection
|
||||
* @param {TemplateMaps} templateMaps
|
||||
* @param {MapBackend} mapBackend
|
||||
* @param metadataBackend
|
||||
* @param {SurrogateKeysCache} surrogateKeysCache
|
||||
* @param {UserLimitsBackend} userLimitsBackend
|
||||
* @param {LayergroupAffectedTables} layergroupAffectedTables
|
||||
* @param {MapConfigAdapter} mapConfigAdapter
|
||||
* @param {StatsBackend} statsBackend
|
||||
* @param {AuthBackend} authBackend
|
||||
* @param layergroupMetadata
|
||||
* @constructor
|
||||
*/
|
||||
constructor (
|
||||
pgConnection,
|
||||
templateMaps,
|
||||
mapBackend,
|
||||
metadataBackend,
|
||||
surrogateKeysCache,
|
||||
userLimitsBackend,
|
||||
layergroupAffectedTables,
|
||||
mapConfigAdapter,
|
||||
statsBackend,
|
||||
authBackend,
|
||||
layergroupMetadata
|
||||
) {
|
||||
this.pgConnection = pgConnection;
|
||||
this.templateMaps = templateMaps;
|
||||
this.mapBackend = mapBackend;
|
||||
this.metadataBackend = metadataBackend;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.userLimitsBackend = userLimitsBackend;
|
||||
this.layergroupAffectedTables = layergroupAffectedTables;
|
||||
this.mapConfigAdapter = mapConfigAdapter;
|
||||
this.statsBackend = statsBackend;
|
||||
this.authBackend = authBackend;
|
||||
this.layergroupMetadata = layergroupMetadata;
|
||||
}
|
||||
|
||||
register (templateRouter) {
|
||||
templateRouter.get('/:template_id/jsonp', this.middlewares());
|
||||
templateRouter.post('/:template_id', this.middlewares());
|
||||
}
|
||||
|
||||
middlewares () {
|
||||
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();
|
||||
});
|
||||
};
|
||||
}
|
64
lib/cartodb/api/template/template-router.js
Normal file
64
lib/cartodb/api/template/template-router.js
Normal 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({ mergeParams: true });
|
||||
|
||||
this.namedMapController.register(templateRouter);
|
||||
this.tileTemplateController.register(templateRouter);
|
||||
this.adminTemplateController.register(templateRouter);
|
||||
|
||||
templatePaths.forEach(path => apiRouter.use(path, templateRouter));
|
||||
}
|
||||
};
|
95
lib/cartodb/api/template/tile-template-controller.js
Normal file
95
lib/cartodb/api/template/tile-template-controller.js
Normal file
@ -0,0 +1,95 @@
|
||||
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;
|
||||
|
||||
module.exports = class TileTemplateController {
|
||||
constructor (
|
||||
namedMapProviderCache,
|
||||
tileBackend,
|
||||
surrogateKeysCache,
|
||||
pgConnection,
|
||||
authBackend,
|
||||
userLimitsBackend
|
||||
) {
|
||||
this.namedMapProviderCache = namedMapProviderCache;
|
||||
this.tileBackend = tileBackend;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.pgConnection = pgConnection;
|
||||
this.authBackend = authBackend;
|
||||
this.userLimitsBackend = userLimitsBackend;
|
||||
}
|
||||
|
||||
register (templateRouter) {
|
||||
templateRouter.get('/:template_id/:layer/:z/:x/:y.(:format)', this.middlewares());
|
||||
}
|
||||
|
||||
middlewares () {
|
||||
return [
|
||||
coordinates(),
|
||||
credentials(),
|
||||
authorize(this.authBackend),
|
||||
dbConnSetup(this.pgConnection),
|
||||
rateLimit(this.userLimitsBackend, RATE_LIMIT_ENDPOINTS_GROUPS.NAMED_TILES),
|
||||
cleanUpQueryParams(),
|
||||
namedMapProvider({
|
||||
namedMapProviderCache: this.namedMapProviderCache,
|
||||
label: 'NAMED_MAP_TILE'
|
||||
}),
|
||||
getTile({
|
||||
tileBackend: this.tileBackend,
|
||||
label: 'NAMED_MAP_TILE'
|
||||
}),
|
||||
setContentTypeHeader(),
|
||||
cacheControlHeader(),
|
||||
cacheChannelHeader(),
|
||||
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
lastModifiedHeader(),
|
||||
vectorError()
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
function getTile ({ tileBackend, label }) {
|
||||
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();
|
||||
};
|
||||
}
|
@ -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) => {
|
@ -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(
|
@ -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)' +
|
@ -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')";
|
||||
});
|
@ -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);
|
||||
};
|
@ -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,
|
||||
|
@ -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')
|
||||
};
|
@ -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();
|
||||
});
|
||||
};
|
||||
}
|
@ -1,199 +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 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 ALLOWED_DATAVIEW_QUERY_PARAMS = [
|
||||
'filters', // json
|
||||
'own_filter', // 0, 1
|
||||
'no_filters', // 0, 1
|
||||
'bbox', // w,s,e,n
|
||||
'start', // number
|
||||
'end', // number
|
||||
'column_type', // string
|
||||
'bins', // number
|
||||
'aggregation', //string
|
||||
'offset', // number
|
||||
'q', // widgets search
|
||||
'categories', // number
|
||||
];
|
||||
|
||||
module.exports = class DataviewController {
|
||||
constructor (
|
||||
dataviewBackend,
|
||||
pgConnection,
|
||||
mapStore,
|
||||
userLimitsApi,
|
||||
layergroupAffectedTablesCache,
|
||||
authApi,
|
||||
surrogateKeysCache
|
||||
) {
|
||||
this.dataviewBackend = dataviewBackend;
|
||||
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;
|
||||
|
||||
// Undocumented/non-supported API endpoint methods.
|
||||
// Use at your own peril.
|
||||
|
||||
app.get(
|
||||
`${mapConfigBasePath}/:token/dataview/:dataviewName`,
|
||||
cors(),
|
||||
user(),
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(this.authApi),
|
||||
dbConnSetup(this.pgConnection),
|
||||
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
|
||||
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
|
||||
createMapStoreMapConfigProvider(
|
||||
this.mapStore,
|
||||
this.userLimitsApi,
|
||||
this.pgConnection,
|
||||
this.layergroupAffectedTablesCache
|
||||
),
|
||||
getDataview(this.dataviewBackend),
|
||||
cacheControlHeader(),
|
||||
cacheChannelHeader(),
|
||||
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
lastModifiedHeader(),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
`${mapConfigBasePath}/:token/:layer/widget/:dataviewName`,
|
||||
cors(),
|
||||
user(),
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(this.authApi),
|
||||
dbConnSetup(this.pgConnection),
|
||||
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW),
|
||||
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
|
||||
createMapStoreMapConfigProvider(
|
||||
this.mapStore,
|
||||
this.userLimitsApi,
|
||||
this.pgConnection,
|
||||
this.layergroupAffectedTablesCache
|
||||
),
|
||||
getDataview(this.dataviewBackend),
|
||||
cacheControlHeader(),
|
||||
cacheChannelHeader(),
|
||||
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
lastModifiedHeader(),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
`${mapConfigBasePath}/:token/dataview/:dataviewName/search`,
|
||||
cors(),
|
||||
user(),
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(this.authApi),
|
||||
dbConnSetup(this.pgConnection),
|
||||
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
|
||||
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
|
||||
createMapStoreMapConfigProvider(
|
||||
this.mapStore,
|
||||
this.userLimitsApi,
|
||||
this.pgConnection,
|
||||
this.layergroupAffectedTablesCache
|
||||
),
|
||||
dataviewSearch(this.dataviewBackend),
|
||||
cacheControlHeader(),
|
||||
cacheChannelHeader(),
|
||||
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
lastModifiedHeader(),
|
||||
sendResponse()
|
||||
);
|
||||
|
||||
app.get(
|
||||
`${mapConfigBasePath}/:token/:layer/widget/:dataviewName/search`,
|
||||
cors(),
|
||||
user(),
|
||||
layergroupToken(),
|
||||
credentials(),
|
||||
authorize(this.authApi),
|
||||
dbConnSetup(this.pgConnection),
|
||||
rateLimit(this.userLimitsApi, RATE_LIMIT_ENDPOINTS_GROUPS.DATAVIEW_SEARCH),
|
||||
cleanUpQueryParams(ALLOWED_DATAVIEW_QUERY_PARAMS),
|
||||
createMapStoreMapConfigProvider(
|
||||
this.mapStore,
|
||||
this.userLimitsApi,
|
||||
this.pgConnection,
|
||||
this.layergroupAffectedTablesCache
|
||||
),
|
||||
dataviewSearch(this.dataviewBackend),
|
||||
cacheControlHeader(),
|
||||
cacheChannelHeader(),
|
||||
surrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
|
||||
lastModifiedHeader(),
|
||||
sendResponse()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function getDataview (dataviewBackend) {
|
||||
return function getDataviewMiddleware (req, res, next) {
|
||||
const { user, mapConfigProvider } = res.locals;
|
||||
const { dataviewName } = req.params;
|
||||
const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals;
|
||||
|
||||
const params = Object.assign({ dataviewName, dbuser, dbname, dbpassword, dbhost, dbport }, req.query);
|
||||
|
||||
dataviewBackend.getDataview(mapConfigProvider, user, params, (err, dataview, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'GET DATAVIEW';
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.body = dataview;
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function dataviewSearch (dataviewBackend) {
|
||||
return function dataviewSearchMiddleware (req, res, next) {
|
||||
const { user, mapConfigProvider } = res.locals;
|
||||
const { dataviewName } = req.params;
|
||||
const { dbuser, dbname, dbpassword, dbhost, dbport } = res.locals;
|
||||
|
||||
const params = Object.assign({ dbuser, dbname, dbpassword, dbhost, dbport }, req.query);
|
||||
|
||||
dataviewBackend.search(mapConfigProvider, user, dataviewName, params, (err, searchResult, stats = {}) => {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'GET DATAVIEW SEARCH';
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.body = searchResult;
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
}
|
@ -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);
|
||||
|
||||
|
||||
};
|
@ -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);
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
@ -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();
|
||||
};
|
||||
};
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
19
lib/cartodb/utils/json-replacer.js
Normal file
19
lib/cartodb/utils/json-replacer.js
Normal 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;
|
||||
};
|
||||
};
|
@ -48,7 +48,7 @@
|
||||
"step-profiler": "~0.3.0",
|
||||
"turbo-carto": "0.20.2",
|
||||
"underscore": "~1.6.0",
|
||||
"windshaft": "4.7.1",
|
||||
"windshaft": "4.7.3",
|
||||
"yargs": "~5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -261,6 +261,41 @@ describe('named-maps analysis', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it('should return and an error requesting unsupported image format', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/static/center/' + layergroupid + '/4/42/-3/320/240.gif',
|
||||
method: 'GET',
|
||||
encoding: 'binary',
|
||||
headers: {
|
||||
host: username
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(
|
||||
JSON.parse(res.body),
|
||||
{
|
||||
errors:['Unsupported image format \"gif\"'],
|
||||
errors_with_context:[{
|
||||
type: 'unknown',
|
||||
message: 'Unsupported image format \"gif\"'
|
||||
}]
|
||||
}
|
||||
);
|
||||
done();
|
||||
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('auto-instantiation', function() {
|
||||
|
@ -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,
|
||||
|
@ -282,4 +282,57 @@ describe('named maps static view', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an error requesting unsupported image format', function (done) {
|
||||
var view = {
|
||||
zoom: 4,
|
||||
center: {
|
||||
lng: 40,
|
||||
lat: 20
|
||||
}
|
||||
};
|
||||
|
||||
templateMaps.addTemplate(username, createTemplate(view), function (err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var url = `/api/v1/map/static/named/${templateName}/640/480.gif`;
|
||||
|
||||
|
||||
var requestOptions = {
|
||||
url: url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: username
|
||||
},
|
||||
encoding: 'binary'
|
||||
};
|
||||
|
||||
var expectedResponse = {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
// this could be removed once named maps are invalidated, otherwise you hits the cache
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
assert.response(server, requestOptions, expectedResponse, function (res, err) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(
|
||||
JSON.parse(res.body),
|
||||
{
|
||||
errors:['Unsupported image format \"gif\"'],
|
||||
errors_with_context:[{
|
||||
type: 'unknown',
|
||||
message: 'Unsupported image format \"gif\"'
|
||||
}]
|
||||
}
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
require('../support/test_helper');
|
||||
var assert = require('../support/assert');
|
||||
const helper = require('../support/test_helper');
|
||||
var TestClient = require('../support/test-client');
|
||||
const LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
|
||||
const CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
|
||||
const serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
|
||||
describe('regressions', function() {
|
||||
|
||||
@ -38,6 +41,49 @@ describe('regressions', function() {
|
||||
});
|
||||
});
|
||||
|
||||
// See: https://github.com/CartoDB/Windshaft-cartodb/pull/956
|
||||
it('"/user/localhost/api/v1/map" should create an anonymous map', function (done) {
|
||||
const server = new CartodbWindshaft(serverOptions);
|
||||
const layergroup = {
|
||||
version: '1.7.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: TestClient.SQL.ONE_POINT,
|
||||
cartocss: TestClient.CARTOCSS.POINTS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const keysToDelete = {};
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/user/localhost/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
const body = JSON.parse(res.body);
|
||||
assert.ok(body.layergroupid);
|
||||
|
||||
keysToDelete['map_cfg|' + LayergroupToken.parse(body.layergroupid).token] = 0;
|
||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
helper.deleteRedisKeys(keysToDelete, done);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('map instantiation', function () {
|
||||
const apikeyToken = 'regular1';
|
||||
const mapConfig = {
|
||||
|
@ -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',
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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, {
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
|
154
yarn.lock
154
yarn.lock
@ -2,20 +2,20 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@carto/mapnik@3.6.2-carto.8":
|
||||
version "3.6.2-carto.8"
|
||||
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.8.tgz#70448689d9b14d644bebd079f5714871c458a46d"
|
||||
"@carto/mapnik@3.6.2-carto.10":
|
||||
version "3.6.2-carto.10"
|
||||
resolved "https://registry.yarnpkg.com/@carto/mapnik/-/mapnik-3.6.2-carto.10.tgz#a97c951dcdac09d0eb35b3ea71e5eeaa206c1af6"
|
||||
dependencies:
|
||||
mapnik-vector-tile cartodb/mapnik-vector-tile#v1.6.1-carto.1
|
||||
nan "2.10.0"
|
||||
node-pre-gyp "0.7.0"
|
||||
node-pre-gyp "0.10.0"
|
||||
protozero "1.5.1"
|
||||
|
||||
"@carto/tilelive-bridge@github:cartodb/tilelive-bridge#2.5.1-cdb7":
|
||||
version "2.5.1-cdb7"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/ec881cb9ac52113f895f23857430e2d434bb99a6"
|
||||
"@carto/tilelive-bridge@github:cartodb/tilelive-bridge#2.5.1-cdb9":
|
||||
version "2.5.1-cdb9"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/5129e43223cb55daed31373c7a36c98eb6178fc1"
|
||||
dependencies:
|
||||
"@carto/mapnik" "3.6.2-carto.8"
|
||||
"@carto/mapnik" "3.6.2-carto.10"
|
||||
"@mapbox/sphericalmercator" "~1.0.1"
|
||||
mapnik-pool "~0.1.3"
|
||||
|
||||
@ -23,11 +23,11 @@
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@mapbox/sphericalmercator/-/sphericalmercator-1.0.5.tgz#70237b9774095ed1cfdbcea7a8fd1fc82b2691f2"
|
||||
|
||||
"abaculus@github:cartodb/abaculus#2.0.3-cdb8":
|
||||
version "2.0.3-cdb8"
|
||||
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/31c03f2442943d4c47740fa154cda753b5cccd8a"
|
||||
"abaculus@github:cartodb/abaculus#2.0.3-cdb10":
|
||||
version "2.0.3-cdb10"
|
||||
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/90d537028bb8af8a35e7a40c46493066dd8a76b3"
|
||||
dependencies:
|
||||
"@carto/mapnik" "3.6.2-carto.8"
|
||||
"@carto/mapnik" "3.6.2-carto.10"
|
||||
d3-queue "^2.0.2"
|
||||
sphericalmercator "1.0.x"
|
||||
|
||||
@ -338,6 +338,10 @@ chalk@^1.1.3:
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chownr@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
|
||||
|
||||
chroma-js@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-1.1.1.tgz#9bb9434959336ece75700aaadfeedc71806d8c05"
|
||||
@ -455,7 +459,7 @@ debug@2.6.0:
|
||||
dependencies:
|
||||
ms "0.7.2"
|
||||
|
||||
debug@2.6.9, debug@^2.2.0:
|
||||
debug@2.6.9, debug@^2.1.2, debug@^2.2.0:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
dependencies:
|
||||
@ -748,6 +752,12 @@ fresh@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
|
||||
fs-minipass@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
|
||||
dependencies:
|
||||
minipass "^2.2.1"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
@ -1005,6 +1015,18 @@ iconv-lite@0.4.19:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||
|
||||
iconv-lite@^0.4.4:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
ignore-walk@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
|
||||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
@ -1363,6 +1385,19 @@ minimist@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce"
|
||||
|
||||
minipass@^2.2.1, minipass@^2.2.4:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.0.tgz#2e11b1c46df7fe7f1afbe9a490280add21ffe384"
|
||||
dependencies:
|
||||
safe-buffer "^5.1.1"
|
||||
yallist "^3.0.0"
|
||||
|
||||
minizlib@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
|
||||
dependencies:
|
||||
minipass "^2.2.1"
|
||||
|
||||
mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
@ -1429,6 +1464,14 @@ ncp@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
|
||||
|
||||
needle@^2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d"
|
||||
dependencies:
|
||||
debug "^2.1.2"
|
||||
iconv-lite "^0.4.4"
|
||||
sax "^1.2.4"
|
||||
|
||||
negotiator@0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
||||
@ -1444,20 +1487,20 @@ nock@~2.11.0:
|
||||
mkdirp "^0.5.0"
|
||||
propagate "0.3.x"
|
||||
|
||||
node-pre-gyp@0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.7.0.tgz#55aeffbaed93b50d0a4657d469198cd80ac9df36"
|
||||
node-pre-gyp@0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46"
|
||||
dependencies:
|
||||
detect-libc "^1.0.2"
|
||||
mkdirp "^0.5.1"
|
||||
needle "^2.2.0"
|
||||
nopt "^4.0.1"
|
||||
npm-packlist "^1.1.6"
|
||||
npmlog "^4.0.2"
|
||||
rc "^1.1.7"
|
||||
request "2.83.0"
|
||||
rimraf "^2.6.1"
|
||||
semver "^5.3.0"
|
||||
tar "^2.2.1"
|
||||
tar-pack "^3.4.0"
|
||||
tar "^4"
|
||||
|
||||
node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.36, node-pre-gyp@~0.6.38:
|
||||
version "0.6.39"
|
||||
@ -1501,6 +1544,17 @@ normalize-package-data@^2.3.2:
|
||||
semver "2 || 3 || 4 || 5"
|
||||
validate-npm-package-license "^3.0.1"
|
||||
|
||||
npm-bundled@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308"
|
||||
|
||||
npm-packlist@^1.1.6:
|
||||
version "1.1.10"
|
||||
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a"
|
||||
dependencies:
|
||||
ignore-walk "^3.0.1"
|
||||
npm-bundled "^1.0.1"
|
||||
|
||||
npmlog@^4.0.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
||||
@ -1904,9 +1958,9 @@ request@2.81.0:
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.0.0"
|
||||
|
||||
request@2.83.0, request@2.x, request@^2.55.0:
|
||||
version "2.83.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
|
||||
request@2.85.0:
|
||||
version "2.85.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
|
||||
dependencies:
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.6.0"
|
||||
@ -1931,9 +1985,9 @@ request@2.83.0, request@2.x, request@^2.55.0:
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.1.0"
|
||||
|
||||
request@2.85.0:
|
||||
version "2.85.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
|
||||
request@2.x, request@^2.55.0:
|
||||
version "2.83.0"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
|
||||
dependencies:
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.6.0"
|
||||
@ -1992,10 +2046,22 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, s
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
|
||||
safe-buffer@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
||||
safe-json-stringify@~1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911"
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
|
||||
sax@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
@ -2247,6 +2313,18 @@ tar@^2.2.1:
|
||||
fstream "^1.0.2"
|
||||
inherits "2"
|
||||
|
||||
tar@^4:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.2.tgz#60685211ba46b38847b1ae7ee1a24d744a2cd462"
|
||||
dependencies:
|
||||
chownr "^1.0.1"
|
||||
fs-minipass "^1.2.5"
|
||||
minipass "^2.2.4"
|
||||
minizlib "^1.1.0"
|
||||
mkdirp "^0.5.0"
|
||||
safe-buffer "^5.1.2"
|
||||
yallist "^3.0.2"
|
||||
|
||||
through2@~0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f"
|
||||
@ -2258,11 +2336,11 @@ 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":
|
||||
version "0.6.18-cdb12"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/e3c0d80e604ca4a5dfad648ee6f6fb355d415a88"
|
||||
"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb14":
|
||||
version "0.6.18-cdb14"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/6d06f728833d3e34d1adcd05567b3f4379f547bb"
|
||||
dependencies:
|
||||
"@carto/mapnik" "3.6.2-carto.8"
|
||||
"@carto/mapnik" "3.6.2-carto.10"
|
||||
generic-pool "~2.4.0"
|
||||
mime "~1.6.0"
|
||||
sphericalmercator "~1.0.4"
|
||||
@ -2419,13 +2497,13 @@ window-size@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
|
||||
|
||||
windshaft@4.7.1:
|
||||
version "4.7.1"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.7.1.tgz#2b92753b2f6e97b239e15e1576ec312cc7dfeb13"
|
||||
windshaft@4.7.3:
|
||||
version "4.7.3"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.7.3.tgz#86b6c9ae21f5ff9ad7a37bc344151e3d13da4f06"
|
||||
dependencies:
|
||||
"@carto/mapnik" "3.6.2-carto.8"
|
||||
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb7
|
||||
abaculus cartodb/abaculus#2.0.3-cdb8
|
||||
"@carto/mapnik" "3.6.2-carto.10"
|
||||
"@carto/tilelive-bridge" cartodb/tilelive-bridge#2.5.1-cdb9
|
||||
abaculus cartodb/abaculus#2.0.3-cdb10
|
||||
canvas cartodb/node-canvas#1.6.2-cdb2
|
||||
carto cartodb/carto#0.15.1-cdb3
|
||||
cartodb-psql "^0.10.1"
|
||||
@ -2439,7 +2517,7 @@ windshaft@4.7.1:
|
||||
sphericalmercator "1.0.4"
|
||||
step "~0.0.6"
|
||||
tilelive "5.12.2"
|
||||
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb12
|
||||
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb14
|
||||
torque.js "~2.11.0"
|
||||
underscore "~1.6.0"
|
||||
|
||||
@ -2480,6 +2558,10 @@ y18n@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
||||
|
||||
yallist@^3.0.0, yallist@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
|
||||
|
||||
yargs-parser@^2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4"
|
||||
|
Loading…
Reference in New Issue
Block a user