Enforce all middlewares to follow the same constructor pattern

This commit is contained in:
Daniel García Aubert
2017-11-03 15:06:15 +01:00
parent 65e8609fec
commit 6acb873d95
+313 -281
View File
@@ -74,356 +74,386 @@ MapController.prototype.composeCreateMapMiddleware = function ({ useTemplate = f
cors(),
userMiddleware,
this.prepareContext,
useTemplate ? checkIntantiteLayergroup : checkCreateLayergroup,
useTemplate ? this.getTemplate.bind(this) : this.prepareAdapterMapConfig.bind(this),
useTemplate ? this.instantiateLayergroup.bind(this) : this.createLayergroup.bind(this),
this.incrementMapViewCount.bind(this),
this.augmentLayergroupData,
this.getAffectedTables.bind(this),
this.setCacheChannel,
this.setLastUpdatedTimeToLayergroup,
this.setCacheControl,
this.setLayerStats.bind(this),
useTemplate ? checkIntantiteLayergroup() : checkCreateLayergroup(),
useTemplate ? this.getTemplate() : this.prepareAdapterMapConfig(),
useTemplate ? this.instantiateLayergroup() : this.createLayergroup(),
this.incrementMapViewCount(),
this.augmentLayergroupData(),
this.getAffectedTables(),
this.setCacheChannel(),
this.setLastUpdatedTimeToLayergroup(),
this.setCacheControl(),
this.setLayerStats(),
this.setLayergroupIdHeaderBuilder(useTemplateHash),
this.setDataviewsAndWidgetsUrlsToLayergroupMetadata.bind(this),
this.setAnalysesMetadataToLayergroupBuilder(includeQuery),
this.setTurboCartoMetadataToLayergroup,
this.setSurrogateKeyHeader.bind(this),
sendResponse,
this.setDataviewsAndWidgetsUrlsToLayergroupMetadata(),
this.setAnalysesMetadataToLayergroup(includeQuery),
this.setTurboCartoMetadataToLayergroup(),
this.setSurrogateKeyHeader(),
sendResponse(),
augmentError({ label, addContext })
];
};
function checkCreateLayergroup (req, res, next) {
req.profiler.start(`windshaft.createmap_${req.method.toLowerCase()}`);
if (req.method === 'POST' && !req.is('application/json')) {
return next(new Error('layergroup POST data must be of type application/json'));
}
function checkCreateLayergroup () {
return function checkCreateLayergroupMiddleware (req, res, next) {
req.profiler.start(`windshaft.createmap_${req.method.toLowerCase()}`);
if (req.method === 'GET') {
const { config } = res.locals;
if (!config) {
return next(new Error('layergroup GET needs a "config" parameter'));
if (req.method === 'POST' && !req.is('application/json')) {
return next(new Error('layergroup POST data must be of type application/json'));
}
try {
req.body = JSON.parse(config);
} catch (err) {
return next(err);
}
}
if (req.method === 'GET') {
const { config } = res.locals;
return next();
}
if (!config) {
return next(new Error('layergroup GET needs a "config" parameter'));
}
function checkIntantiteLayergroup(req, res, next) {
// jshint maxcomplexity: 7
req.profiler.start(`windshaft-cartodb.instance_template_${req.method.toLowerCase()}`);
if (req.method === 'POST' && !req.is('application/json')) {
return next(new Error('Template POST data must be of type application/json'));
}
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'));
} catch (err) {
return next(err);
}
}
}
return next();
}
MapController.prototype.prepareAdapterMapConfig = function (req, res, next) {
const requestMapConfig = req.body;
const { user, dbhost, dbport, dbname, dbuser, dbpassword, api_key } = res.locals;
const context = {
analysisConfiguration: {
user,
db: {
host: dbhost,
port: dbport,
dbname: dbname,
user: dbuser,
pass: dbpassword
},
batch: {
username: user,
apiKey: api_key
}
}
return next();
};
this.mapConfigAdapter.getMapConfig(user, requestMapConfig, res.locals, context, (err, requestMapConfig) => {
if (err) {
return next(err);
}
req.body = requestMapConfig;
res.locals.context = context;
next();
});
};
MapController.prototype.createLayergroup = function(req, res, next) {
const requestMapConfig = req.body;
const { context, user } = res.locals;
const datasource = context.datasource || Datasource.EmptyDatasource();
const mapconfig = new MapConfig(requestMapConfig, datasource);
const mapconfigProvider = new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, res.locals);
res.locals.mapconfig = mapconfig;
res.locals.analysesResults = context.analysesResults;
this.mapBackend.createLayergroup(mapconfig, res.locals, mapconfigProvider, (err, layergroup) => {
if (err) {
return next(err);
}
res.locals.layergroup = layergroup;
next();
});
};
MapController.prototype.getTemplate = function(req, res, next) {
const templateParams = req.body;
const { user } = res.locals;
const mapconfigProvider = new NamedMapMapConfigProvider(
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsApi,
this.mapConfigAdapter,
user,
req.params.template_id,
templateParams,
res.locals.auth_token,
res.locals
);
mapconfigProvider.getMapConfig((err, mapconfig, rendererParams) => {
if (err) {
return next(err);
}
res.locals.mapconfig = mapconfig;
res.locals.rendererParams = rendererParams;
res.locals.mapconfigProvider = mapconfigProvider;
next();
});
};
MapController.prototype.instantiateLayergroup = function(req, res, next) {
const { user, mapconfig, rendererParams } = res.locals;
const mapconfigProvider =
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, rendererParams);
this.mapBackend.createLayergroup(mapconfig, rendererParams, mapconfigProvider, (err, layergroup) => {
if (err) {
return next(err);
}
res.locals.layergroup = layergroup;
// TODO: Do not provide shortcuts
const { mapconfigProvider } = res.locals;
res.locals.analysesResults = mapconfigProvider.analysesResults;
res.locals.template = mapconfigProvider.template;
res.locals.templateName = mapconfigProvider.getTemplateName();
res.locals.context = mapconfigProvider.context;
next();
});
};
function sendResponse (req, res) {
const { layergroup } = res.locals;
res.status(200);
if (req.query && req.query.callback) {
res.jsonp(layergroup);
} else {
res.json(layergroup);
}
}
MapController.prototype.incrementMapViewCount = function (req, res, callback) {
const { mapconfig, user } = res.locals;
function checkIntantiteLayergroup () {
return function checkIntantiteLayergroupMiddleware(req, res, next) {
// jshint maxcomplexity: 7
req.profiler.start(`windshaft-cartodb.instance_template_${req.method.toLowerCase()}`);
// Error won't blow up, just be logged.
this.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}`);
if (req.method === 'POST' && !req.is('application/json')) {
return next(new Error('Template POST data must be of type application/json'));
}
callback();
});
};
if (req.method === 'GET') {
const { callback, config } = req.query;
MapController.prototype.augmentLayergroupData = function (req, res, callback) {
const { layergroup } = res.locals;
// 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);
callback();
};
MapController.prototype.getAffectedTables = function (req, res, next) {
const { dbname, layergroup, user, mapconfig } = res.locals;
this.pgConnection.getConnection(user, (err, connection) => {
if (err) {
return next(err);
}
const sql = [];
mapconfig.getLayers().forEach(function(layer) {
sql.push(layer.options.sql);
if (layer.options.affected_tables) {
layer.options.affected_tables.map(function(table) {
sql.push('SELECT * FROM ' + table + ' LIMIT 0');
});
if (callback === undefined || callback.length === 0) {
return next(new Error('callback parameter should be present and be a function name'));
}
});
QueryTables.getAffectedTablesFromQuery(connection, sql.join(';'), (err, affectedTables) => {
if (config) {
try {
req.body = JSON.parse(config);
} catch(e) {
return next(new Error('Invalid config parameter, should be a valid JSON'));
}
}
}
return next();
};
}
MapController.prototype.prepareAdapterMapConfig = function () {
return function prepareAdapterMapConfigMiddleware(req, res, next) {
const requestMapConfig = req.body;
const { user, dbhost, dbport, dbname, dbuser, dbpassword, api_key } = res.locals;
const context = {
analysisConfiguration: {
user,
db: {
host: dbhost,
port: dbport,
dbname: dbname,
user: dbuser,
pass: dbpassword
},
batch: {
username: user,
apiKey: api_key
}
}
};
this.mapConfigAdapter.getMapConfig(user, requestMapConfig, res.locals, context, (err, requestMapConfig) => {
if (err) {
return next(err);
}
// feed affected tables cache so it can be reused from, for instance, layergroup controller
this.layergroupAffectedTables.set(dbname, layergroup.layergroupId, affectedTables);
res.locals.affectedTables = affectedTables;
req.body = requestMapConfig;
res.locals.context = context;
next();
});
});
}.bind(this);
};
MapController.prototype.setCacheChannel = function (req, res, callback) {
const { affectedTables } = res.locals;
MapController.prototype.createLayergroup = function () {
return function createLayergroupMiddleware (req, res, next) {
const requestMapConfig = req.body;
const { context, user } = res.locals;
const datasource = context.datasource || Datasource.EmptyDatasource();
const mapconfig = new MapConfig(requestMapConfig, datasource);
const mapconfigProvider = new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, res.locals);
if (req.method === 'GET') {
res.set('Last-Modified', (new Date()).toUTCString());
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
}
res.locals.mapconfig = mapconfig;
res.locals.analysesResults = context.analysesResults;
callback();
this.mapBackend.createLayergroup(mapconfig, res.locals, mapconfigProvider, (err, layergroup) => {
if (err) {
return next(err);
}
res.locals.layergroup = layergroup;
next();
});
}.bind(this);
};
MapController.prototype.setLastUpdatedTimeToLayergroup = function (req, res, callback) {
const { affectedTables, layergroup, analysesResults } = res.locals;
MapController.prototype.getTemplate = function () {
return function getTemplateMiddleware (req, res, next) {
const templateParams = req.body;
const { user } = res.locals;
var lastUpdateTime = affectedTables.getLastUpdatedAt();
const mapconfigProvider = new NamedMapMapConfigProvider(
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsApi,
this.mapConfigAdapter,
user,
req.params.template_id,
templateParams,
res.locals.auth_token,
res.locals
);
lastUpdateTime = getLastUpdatedTime(analysesResults, lastUpdateTime) || lastUpdateTime;
mapconfigProvider.getMapConfig((err, mapconfig, rendererParams) => {
if (err) {
return next(err);
}
// last update for layergroup cache buster
layergroup.layergroupid = layergroup.layergroupid + ':' + lastUpdateTime;
layergroup.last_updated = new Date(lastUpdateTime).toISOString();
res.locals.mapconfig = mapconfig;
res.locals.rendererParams = rendererParams;
res.locals.mapconfigProvider = mapconfigProvider;
callback();
next();
});
}.bind(this);
};
MapController.prototype.setCacheControl = function (req, res, callback) {
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
}
MapController.prototype.instantiateLayergroup = function () {
return function instantiateLayergroupMiddleware (req, res, next) {
const { user, mapconfig, rendererParams } = res.locals;
const mapconfigProvider =
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, rendererParams);
callback();
this.mapBackend.createLayergroup(mapconfig, rendererParams, mapconfigProvider, (err, layergroup) => {
if (err) {
return next(err);
}
res.locals.layergroup = layergroup;
// TODO: Do not provide shortcuts
const { mapconfigProvider } = res.locals;
res.locals.analysesResults = mapconfigProvider.analysesResults;
res.locals.template = mapconfigProvider.template;
res.locals.templateName = mapconfigProvider.getTemplateName();
res.locals.context = mapconfigProvider.context;
next();
});
}.bind(this);
};
MapController.prototype.setLayerStats = function (req, res, callback) {
const { user, mapconfig, layergroup } = res.locals;
function sendResponse() {
return function sendResponseMiddleware (req, res) {
const { layergroup } = res.locals;
this.pgConnection.getConnection(user, (err, connection) => {
if (err) {
return callback(err);
res.status(200);
if (req.query && req.query.callback) {
res.jsonp(layergroup);
} else {
res.json(layergroup);
}
};
}
MapController.prototype.incrementMapViewCount = function () {
return function incrementMapViewCountMiddleware(req, res, next) {
const { mapconfig, user } = res.locals;
// Error won't blow up, just be logged.
this.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();
});
}.bind(this);
};
MapController.prototype.augmentLayergroupData = function () {
return function augmentLayergroupDataMiddleware (req, res, next) {
const { layergroup } = res.locals;
// 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();
};
};
MapController.prototype.getAffectedTables = function () {
return function getAffectedTablesMiddleware (req, res, next) {
const { dbname, layergroup, user, mapconfig } = res.locals;
this.pgConnection.getConnection(user, (err, connection) => {
if (err) {
return next(err);
}
const sql = [];
mapconfig.getLayers().forEach(function(layer) {
sql.push(layer.options.sql);
if (layer.options.affected_tables) {
layer.options.affected_tables.map(function(table) {
sql.push('SELECT * FROM ' + table + ' LIMIT 0');
});
}
});
QueryTables.getAffectedTablesFromQuery(connection, sql.join(';'), (err, affectedTables) => {
if (err) {
return next(err);
}
// feed affected tables cache so it can be reused from, for instance, layergroup controller
this.layergroupAffectedTables.set(dbname, layergroup.layergroupId, affectedTables);
res.locals.affectedTables = affectedTables;
next();
});
});
}.bind(this);
};
MapController.prototype.setCacheChannel = function () {
return function setCacheChannelMiddleware (req, res, next) {
const { affectedTables } = res.locals;
if (req.method === 'GET') {
res.set('Last-Modified', (new Date()).toUTCString());
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
}
this.statsBackend.getStats(mapconfig, connection, function(err, layersStats) {
next();
};
};
MapController.prototype.setLastUpdatedTimeToLayergroup = function () {
return function setLastUpdatedTimeToLayergroupMiddleware (req, res, next) {
const { affectedTables, layergroup, analysesResults } = res.locals;
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();
};
};
MapController.prototype.setCacheControl = function () {
return function setCacheControlMiddleware (req, res, next) {
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
}
next();
};
};
MapController.prototype.setLayerStats = function () {
return function setLayerStatsMiddleware(req, res, next) {
const { user, mapconfig, layergroup } = res.locals;
this.pgConnection.getConnection(user, (err, connection) => {
if (err) {
return callback(err);
return next(err);
}
if (layersStats.length > 0) {
layergroup.metadata.layers.forEach(function (layer, index) {
layer.meta.stats = layersStats[index];
});
}
this.statsBackend.getStats(mapconfig, connection, function(err, layersStats) {
if (err) {
return next(err);
}
callback();
if (layersStats.length > 0) {
layergroup.metadata.layers.forEach(function (layer, index) {
layer.meta.stats = layersStats[index];
});
}
next();
});
});
});
}.bind(this);
};
MapController.prototype.setLayergroupIdHeaderBuilder = function (useTemplateHash) {
const self = this;
return function setLayergroupIdHeader(req, res, callback) {
return function setLayergroupIdHeaderMiddleware (req, res, next) {
const { layergroup, user, template } = res.locals;
if (useTemplateHash) {
var templateHash = self.templateMaps.fingerPrint(template).substring(0, 8);
var templateHash = this.templateMaps.fingerPrint(template).substring(0, 8);
layergroup.layergroupid = `${user}@${templateHash}@${layergroup.layergroupid}`;
}
res.set('X-Layergroup-Id', layergroup.layergroupid);
callback();
};
next();
}.bind();
};
MapController.prototype.setDataviewsAndWidgetsUrlsToLayergroupMetadata = function (req, res, callback) {
const { layergroup, user, mapconfig } = res.locals;
MapController.prototype.setDataviewsAndWidgetsUrlsToLayergroupMetadata = function () {
return function setDataviewsAndWidgetsUrlsToLayergroupMetadataMiddleware (req, res, next) {
const { layergroup, user, mapconfig } = res.locals;
this.addDataviewsAndWidgetsUrls(user, layergroup, mapconfig.obj());
this.addDataviewsAndWidgetsUrls(user, layergroup, mapconfig.obj());
callback();
next();
}.bind(this);
};
MapController.prototype.setAnalysesMetadataToLayergroupBuilder = function (includeQuery) {
const self = this;
return function setAnalysesMetadataToLayergroup (req, res, callback) {
MapController.prototype.setAnalysesMetadataToLayergroup = function (includeQuery) {
return function setAnalysesMetadataToLayergroupMiddleware (req, res, next) {
const { layergroup, user, analysesResults = [] } = res.locals;
self.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery);
this.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery);
callback();
};
next();
}.bind(this);
};
MapController.prototype.setTurboCartoMetadataToLayergroup = function (req, res, callback) {
const { layergroup, mapconfig, context } = res.locals;
MapController.prototype.setTurboCartoMetadataToLayergroup = function () {
return function (req, res, next) {
const { layergroup, mapconfig, context } = res.locals;
addContextMetadata(layergroup, mapconfig.obj(), context);
addContextMetadata(layergroup, mapconfig.obj(), context);
callback();
next();
};
};
function addContextMetadata(layergroup, mapConfig, context) {
@@ -437,18 +467,20 @@ function addContextMetadata(layergroup, mapConfig, context) {
}
}
MapController.prototype.setSurrogateKeyHeader = function (req, res, callback) {
const { affectedTables, user, templateName } = res.locals;
MapController.prototype.setSurrogateKeyHeader = function () {
return function setSurrogateKeyHeaderMiddleware(req, res, next) {
const { affectedTables, user, templateName } = res.locals;
if (req.method === 'GET' && affectedTables.tables && affectedTables.tables.length > 0) {
this.surrogateKeysCache.tag(res, affectedTables);
}
if (req.method === 'GET' && affectedTables.tables && affectedTables.tables.length > 0) {
this.surrogateKeysCache.tag(res, affectedTables);
}
if (templateName) {
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, templateName));
}
if (templateName) {
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, templateName));
}
callback();
next();
}.bind(this);
};
function getLastUpdatedTime(analysesResults, lastUpdateTime) {
@@ -534,7 +566,7 @@ MapController.prototype.addWidgetsUrl = function(username, layergroup, mapConfig
function augmentError (options) {
const { addContext = false, label = 'MAPS CONTROLLER' } = options;
return function mapError (err, req, res, next) {
return function augmentErrorMiddleware (err, req, res, next) {
const { mapconfig } = res.locals;
if (addContext) {