2015-07-09 20:39:25 +08:00
|
|
|
var _ = require('underscore');
|
2015-07-10 01:49:11 +08:00
|
|
|
var crypto = require('crypto');
|
2015-07-10 00:47:21 +08:00
|
|
|
var dot = require('dot');
|
|
|
|
var MapConfig = require('windshaft').model.MapConfig;
|
2016-05-23 19:32:28 +08:00
|
|
|
var templateName = require('../../../backends/template_maps').templateName;
|
2016-02-23 02:11:54 +08:00
|
|
|
var QueryTables = require('cartodb-query-tables');
|
2015-07-09 20:39:25 +08:00
|
|
|
|
2015-07-10 18:31:56 +08:00
|
|
|
/**
|
|
|
|
* @constructor
|
|
|
|
* @type {NamedMapMapConfigProvider}
|
|
|
|
*/
|
2018-03-21 21:11:54 +08:00
|
|
|
function NamedMapMapConfigProvider(
|
|
|
|
templateMaps,
|
|
|
|
pgConnection,
|
|
|
|
metadataBackend,
|
2018-04-10 16:16:07 +08:00
|
|
|
userLimitsBackend,
|
2018-03-21 21:11:54 +08:00
|
|
|
mapConfigAdapter,
|
|
|
|
affectedTablesCache,
|
|
|
|
owner,
|
|
|
|
templateId,
|
|
|
|
config,
|
|
|
|
authToken,
|
|
|
|
params
|
|
|
|
) {
|
2015-07-09 20:39:25 +08:00
|
|
|
this.templateMaps = templateMaps;
|
|
|
|
this.pgConnection = pgConnection;
|
2016-04-07 22:18:48 +08:00
|
|
|
this.metadataBackend = metadataBackend;
|
2018-04-10 16:16:07 +08:00
|
|
|
this.userLimitsBackend = userLimitsBackend;
|
2016-05-24 05:29:06 +08:00
|
|
|
this.mapConfigAdapter = mapConfigAdapter;
|
2016-05-24 03:56:38 +08:00
|
|
|
|
2015-07-09 20:39:25 +08:00
|
|
|
this.owner = owner;
|
2015-07-10 00:47:21 +08:00
|
|
|
this.templateName = templateName(templateId);
|
2015-07-09 20:39:25 +08:00
|
|
|
this.config = config;
|
|
|
|
this.authToken = authToken;
|
|
|
|
this.params = params;
|
|
|
|
|
2015-07-15 03:16:49 +08:00
|
|
|
this.cacheBuster = Date.now();
|
|
|
|
|
2015-07-09 20:39:25 +08:00
|
|
|
// use template after call to mapConfig
|
|
|
|
this.template = null;
|
|
|
|
|
2018-03-21 21:11:54 +08:00
|
|
|
this.affectedTablesCache = affectedTablesCache;
|
2015-07-15 02:10:55 +08:00
|
|
|
|
2015-07-09 20:39:25 +08:00
|
|
|
// providing
|
|
|
|
this.err = null;
|
|
|
|
this.mapConfig = null;
|
|
|
|
this.rendererParams = null;
|
2015-07-11 01:10:55 +08:00
|
|
|
this.context = {};
|
2016-04-21 23:03:41 +08:00
|
|
|
this.analysesResults = [];
|
2015-07-09 20:39:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = NamedMapMapConfigProvider;
|
|
|
|
|
|
|
|
NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
|
2015-07-10 00:47:21 +08:00
|
|
|
if (!!this.err || this.mapConfig !== null) {
|
2015-07-09 20:39:25 +08:00
|
|
|
return callback(this.err, this.mapConfig, this.rendererParams, this.context);
|
|
|
|
}
|
|
|
|
|
2016-05-23 22:20:42 +08:00
|
|
|
var context = {};
|
|
|
|
|
2018-05-31 00:18:42 +08:00
|
|
|
this.getTemplate((err, template) => {
|
2018-05-31 00:15:51 +08:00
|
|
|
if (err) {
|
|
|
|
this.err = err;
|
|
|
|
return callback(err);
|
|
|
|
}
|
2015-07-09 20:39:25 +08:00
|
|
|
|
2018-05-31 00:31:59 +08:00
|
|
|
this.getDBParams(this.owner, (err, rendererParams) => {
|
2018-05-31 00:15:51 +08:00
|
|
|
if (err) {
|
|
|
|
this.err = err;
|
|
|
|
return callback(err);
|
2015-07-09 20:39:25 +08:00
|
|
|
}
|
|
|
|
|
2018-05-31 00:37:11 +08:00
|
|
|
this.metadataBackend.getUserMapKey(this.owner, (err, apiKey) => {
|
2018-05-31 00:15:51 +08:00
|
|
|
if (err) {
|
|
|
|
this.err = err;
|
|
|
|
return callback(err);
|
2016-04-07 22:18:48 +08:00
|
|
|
}
|
2018-05-31 00:15:51 +08:00
|
|
|
|
|
|
|
var templateParams = {};
|
|
|
|
if (this.config) {
|
|
|
|
try {
|
|
|
|
templateParams = _.isString(this.config) ? JSON.parse(this.config) : this.config;
|
|
|
|
} catch (e) {
|
|
|
|
const error = new Error('malformed config parameter, should be a valid JSON');
|
|
|
|
this.err = error;
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
context.templateParams = templateParams;
|
|
|
|
|
|
|
|
let requestMapConfig;
|
|
|
|
try {
|
2018-05-31 00:18:42 +08:00
|
|
|
requestMapConfig = this.templateMaps.instance(template, templateParams);
|
2018-05-31 00:15:51 +08:00
|
|
|
} catch (err) {
|
|
|
|
this.err = err;
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
context.analysisConfiguration = {
|
|
|
|
user: this.owner,
|
|
|
|
db: {
|
|
|
|
host: rendererParams.dbhost,
|
|
|
|
port: rendererParams.dbport,
|
|
|
|
dbname: rendererParams.dbname,
|
|
|
|
user: rendererParams.dbuser,
|
|
|
|
pass: rendererParams.dbpassword
|
|
|
|
},
|
|
|
|
batch: {
|
|
|
|
username: this.owner,
|
|
|
|
apiKey: apiKey
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-05-31 00:37:11 +08:00
|
|
|
this.mapConfigAdapter.getMapConfig(this.owner, requestMapConfig, rendererParams, context, (err, mapConfig) => {
|
2018-05-31 00:15:51 +08:00
|
|
|
if (err) {
|
|
|
|
this.err = err;
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.userLimitsBackend.getRenderLimits(this.owner, this.params.api_key, (err, renderLimits) => {
|
|
|
|
if (err) {
|
|
|
|
this.err = err;
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.mapConfig = (mapConfig === null) ? null : new MapConfig(mapConfig, context.datasource);
|
|
|
|
this.analysesResults = context.analysesResults || [];
|
|
|
|
this.rendererParams = rendererParams;
|
|
|
|
this.context = context;
|
|
|
|
this.context.limits = renderLimits || {};
|
|
|
|
|
|
|
|
return callback(null, this.mapConfig, this.rendererParams, this.context);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2015-07-09 20:39:25 +08:00
|
|
|
};
|
|
|
|
|
2015-07-10 00:47:21 +08:00
|
|
|
NamedMapMapConfigProvider.prototype.getTemplate = function(callback) {
|
|
|
|
if (!!this.err || this.template !== null) {
|
|
|
|
return callback(this.err, this.template);
|
|
|
|
}
|
|
|
|
|
2018-05-31 00:15:51 +08:00
|
|
|
this.templateMaps.getTemplate(this.owner, this.templateName, (err, tpl) => {
|
|
|
|
if (err) {
|
|
|
|
this.err = err;
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!tpl) {
|
|
|
|
var notFoundErr = new Error(
|
|
|
|
"Template '" + this.templateName + "' of user '" + this.owner + "' not found"
|
|
|
|
);
|
|
|
|
notFoundErr.http_status = 404;
|
|
|
|
|
|
|
|
this.err = notFoundErr;
|
|
|
|
|
|
|
|
return callback(notFoundErr);
|
|
|
|
}
|
2015-07-10 00:47:21 +08:00
|
|
|
|
2018-05-31 00:15:51 +08:00
|
|
|
var authorized = false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
authorized = this.templateMaps.isAuthorized(tpl, this.authToken);
|
|
|
|
} catch (err) {
|
|
|
|
// we catch to add http_status
|
|
|
|
var authorizationFailedErr = new Error('Failed to authorize template');
|
|
|
|
authorizationFailedErr.http_status = 403;
|
|
|
|
|
|
|
|
this.err = authorizationFailedErr;
|
|
|
|
|
|
|
|
return callback(authorizationFailedErr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!authorized) {
|
|
|
|
var unauthorizedErr = new Error('Unauthorized template instantiation');
|
|
|
|
unauthorizedErr.http_status = 403;
|
|
|
|
this.err = unauthorizedErr;
|
|
|
|
|
|
|
|
return callback(unauthorizedErr);
|
2015-07-10 00:47:21 +08:00
|
|
|
}
|
2018-05-31 00:15:51 +08:00
|
|
|
|
|
|
|
this.template = tpl;
|
|
|
|
|
|
|
|
return callback(null, this.template);
|
|
|
|
});
|
2015-07-10 00:47:21 +08:00
|
|
|
};
|
|
|
|
|
2015-07-09 20:39:25 +08:00
|
|
|
NamedMapMapConfigProvider.prototype.getKey = function() {
|
2015-07-10 18:31:56 +08:00
|
|
|
return this.createKey(false);
|
2015-07-09 20:39:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
NamedMapMapConfigProvider.prototype.getCacheBuster = function() {
|
2015-07-15 03:16:49 +08:00
|
|
|
return this.cacheBuster;
|
2015-07-09 20:39:25 +08:00
|
|
|
};
|
|
|
|
|
2015-09-23 22:45:20 +08:00
|
|
|
NamedMapMapConfigProvider.prototype.reset = function() {
|
|
|
|
this.template = null;
|
|
|
|
|
2018-03-21 21:11:54 +08:00
|
|
|
this.affectedTables = null;
|
2015-09-23 22:45:20 +08:00
|
|
|
|
|
|
|
this.err = null;
|
|
|
|
this.mapConfig = null;
|
|
|
|
|
|
|
|
this.cacheBuster = Date.now();
|
|
|
|
};
|
|
|
|
|
2015-07-09 20:39:25 +08:00
|
|
|
NamedMapMapConfigProvider.prototype.filter = function(key) {
|
2015-07-10 00:47:21 +08:00
|
|
|
var regex = new RegExp('^' + this.createKey(true) + '.*');
|
2015-07-09 20:39:25 +08:00
|
|
|
return key && key.match(regex);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Configure bases for cache keys suitable for string interpolation
|
2015-07-14 20:30:37 +08:00
|
|
|
var baseKey = '{{=it.dbname}}:{{=it.owner}}:{{=it.templateName}}';
|
2015-07-10 00:47:21 +08:00
|
|
|
var rendererKey = baseKey + ':{{=it.authToken}}:{{=it.configHash}}:{{=it.format}}:{{=it.layer}}:{{=it.scale_factor}}';
|
|
|
|
|
|
|
|
var baseKeyTpl = dot.template(baseKey);
|
|
|
|
var rendererKeyTpl = dot.template(rendererKey);
|
|
|
|
|
|
|
|
NamedMapMapConfigProvider.prototype.createKey = function(base) {
|
|
|
|
var tplValues = _.defaults({}, this.params, {
|
2015-07-09 20:39:25 +08:00
|
|
|
dbname: '',
|
2015-07-10 00:47:21 +08:00
|
|
|
owner: this.owner,
|
|
|
|
templateName: this.templateName,
|
|
|
|
authToken: this.authToken || '',
|
2015-07-10 01:49:11 +08:00
|
|
|
configHash: configHash(this.config),
|
2015-07-10 00:47:21 +08:00
|
|
|
layer: '',
|
2015-07-09 20:39:25 +08:00
|
|
|
scale_factor: 1
|
2015-07-10 00:47:21 +08:00
|
|
|
});
|
|
|
|
return (base) ? baseKeyTpl(tplValues) : rendererKeyTpl(tplValues);
|
|
|
|
};
|
2015-07-09 20:39:25 +08:00
|
|
|
|
2015-07-10 01:49:11 +08:00
|
|
|
function configHash(config) {
|
|
|
|
if (!config) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
return crypto.createHash('md5').update(JSON.stringify(config)).digest('hex').substring(0,8);
|
|
|
|
}
|
|
|
|
|
2015-07-15 03:16:49 +08:00
|
|
|
module.exports.configHash = configHash;
|
|
|
|
|
2018-05-31 00:31:59 +08:00
|
|
|
NamedMapMapConfigProvider.prototype.getDBParams = function(cdbuser, callback) {
|
|
|
|
const dbParams = _.extend({}, this.params, {
|
|
|
|
user: this.owner
|
|
|
|
});
|
|
|
|
|
2018-02-08 19:04:03 +08:00
|
|
|
this.pgConnection.getDatabaseParams(cdbuser, (err, databaseParams) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
2015-07-09 20:39:25 +08:00
|
|
|
}
|
2018-02-08 19:04:03 +08:00
|
|
|
|
2018-05-31 00:31:59 +08:00
|
|
|
dbParams.dbuser = databaseParams.dbuser;
|
|
|
|
dbParams.dbpass = databaseParams.dbpass;
|
|
|
|
dbParams.dbhost = databaseParams.dbhost;
|
|
|
|
dbParams.dbport = databaseParams.dbport;
|
|
|
|
dbParams.dbname = databaseParams.dbname;
|
2018-02-08 19:04:03 +08:00
|
|
|
|
2018-05-31 00:31:59 +08:00
|
|
|
return callback(null, dbParams);
|
2018-02-08 19:04:03 +08:00
|
|
|
});
|
2015-07-10 00:47:21 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
NamedMapMapConfigProvider.prototype.getTemplateName = function() {
|
|
|
|
return this.templateName;
|
|
|
|
};
|
2015-07-15 02:10:55 +08:00
|
|
|
|
2018-04-06 18:59:53 +08:00
|
|
|
NamedMapMapConfigProvider.prototype.createAffectedTables = function(callback) {
|
2018-03-22 02:08:37 +08:00
|
|
|
this.getMapConfig((err, mapConfig) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2015-07-15 02:10:55 +08:00
|
|
|
|
2018-03-22 02:08:37 +08:00
|
|
|
const { dbname } = this.rendererParams;
|
|
|
|
const token = mapConfig.id();
|
2015-07-15 02:10:55 +08:00
|
|
|
|
2018-03-22 02:08:37 +08:00
|
|
|
const queries = [];
|
2018-03-21 21:11:54 +08:00
|
|
|
|
2018-03-22 02:08:37 +08:00
|
|
|
mapConfig.getLayers().forEach(layer => {
|
|
|
|
queries.push(layer.options.sql);
|
|
|
|
if (layer.options.affected_tables) {
|
|
|
|
layer.options.affected_tables.map(table => {
|
|
|
|
queries.push(`SELECT * FROM ${table} LIMIT 0`);
|
|
|
|
});
|
2018-03-21 21:11:54 +08:00
|
|
|
}
|
2018-03-22 02:08:37 +08:00
|
|
|
});
|
2018-03-21 21:11:54 +08:00
|
|
|
|
2018-03-22 02:08:37 +08:00
|
|
|
const sql = queries.length ? queries.join(';') : null;
|
2018-03-21 21:11:54 +08:00
|
|
|
|
2018-03-22 02:08:37 +08:00
|
|
|
if (!sql) {
|
|
|
|
return callback();
|
|
|
|
}
|
2018-03-21 21:11:54 +08:00
|
|
|
|
2018-03-22 02:08:37 +08:00
|
|
|
this.pgConnection.getConnection(this.owner, (err, connection) => {
|
2018-03-21 21:11:54 +08:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
2018-03-22 02:08:37 +08:00
|
|
|
QueryTables.getAffectedTablesFromQuery(connection, sql, (err, affectedTables) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2018-03-21 21:11:54 +08:00
|
|
|
|
2018-03-22 02:08:37 +08:00
|
|
|
this.affectedTablesCache.set(dbname, token, affectedTables);
|
|
|
|
|
|
|
|
callback(err, affectedTables);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2015-07-15 02:10:55 +08:00
|
|
|
};
|
2018-04-06 18:59:53 +08:00
|
|
|
|
|
|
|
NamedMapMapConfigProvider.prototype.getAffectedTables = function (callback) {
|
|
|
|
this.getMapConfig((err, mapConfig) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
const { dbname } = this.params;
|
|
|
|
const token = mapConfig.id();
|
|
|
|
|
|
|
|
if (this.affectedTablesCache.hasAffectedTables(dbname, token)) {
|
|
|
|
const affectedTables = this.affectedTablesCache.get(dbname, token);
|
|
|
|
return callback(null, affectedTables);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.createAffectedTables(callback);
|
|
|
|
});
|
|
|
|
};
|