Windshaft-cartodb/lib/cartodb/models/mapconfig/provider/named-map-provider.js

329 lines
9.8 KiB
JavaScript
Raw Normal View History

var _ = require('underscore');
var crypto = require('crypto');
var dot = require('dot');
var MapConfig = require('windshaft').model.MapConfig;
var templateName = require('../../../backends/template_maps').templateName;
var QueryTables = require('cartodb-query-tables');
/**
* @constructor
* @type {NamedMapMapConfigProvider}
*/
function NamedMapMapConfigProvider(
templateMaps,
pgConnection,
metadataBackend,
userLimitsBackend,
mapConfigAdapter,
affectedTablesCache,
owner,
templateId,
config,
authToken,
params
) {
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.userLimitsBackend = userLimitsBackend;
2016-05-24 05:29:06 +08:00
this.mapConfigAdapter = mapConfigAdapter;
2016-05-24 03:56:38 +08:00
this.owner = owner;
this.templateName = templateName(templateId);
this.config = config;
this.authToken = authToken;
this.params = params;
this.cacheBuster = Date.now();
// use template after call to mapConfig
this.template = null;
this.affectedTablesCache = affectedTablesCache;
// providing
this.err = null;
this.mapConfig = null;
this.rendererParams = null;
2015-07-11 01:10:55 +08:00
this.context = {};
this.analysesResults = [];
}
module.exports = NamedMapMapConfigProvider;
NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
if (!!this.err || this.mapConfig !== null) {
return callback(this.err, this.mapConfig, this.rendererParams, this.context);
}
2016-05-23 22:20:42 +08:00
var context = {};
this.getTemplate((err, template) => {
2018-05-31 00:15:51 +08:00
if (err) {
this.err = err;
return callback(err);
}
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);
}
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);
}
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 {
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);
});
});
});
});
});
};
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);
}
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);
}
2018-05-31 00:15:51 +08:00
this.template = tpl;
return callback(null, this.template);
});
};
NamedMapMapConfigProvider.prototype.getKey = function() {
return this.createKey(false);
};
NamedMapMapConfigProvider.prototype.getCacheBuster = function() {
return this.cacheBuster;
};
NamedMapMapConfigProvider.prototype.reset = function() {
this.template = null;
this.affectedTables = null;
this.err = null;
this.mapConfig = null;
this.cacheBuster = Date.now();
};
NamedMapMapConfigProvider.prototype.filter = function(key) {
var regex = new RegExp('^' + this.createKey(true) + '.*');
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}}';
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, {
dbname: '',
owner: this.owner,
templateName: this.templateName,
authToken: this.authToken || '',
configHash: configHash(this.config),
layer: '',
scale_factor: 1
});
return (base) ? baseKeyTpl(tplValues) : rendererKeyTpl(tplValues);
};
function configHash(config) {
if (!config) {
return '';
}
return crypto.createHash('md5').update(JSON.stringify(config)).digest('hex').substring(0,8);
}
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);
}
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
});
};
NamedMapMapConfigProvider.prototype.getTemplateName = function() {
return this.templateName;
};
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);
}
2018-03-22 02:08:37 +08:00
const { dbname } = this.rendererParams;
const token = mapConfig.id();
2018-03-22 02:08:37 +08:00
const queries = [];
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-22 02:08:37 +08:00
});
2018-03-22 02:08:37 +08:00
const sql = queries.length ? queries.join(';') : null;
2018-03-22 02:08:37 +08:00
if (!sql) {
return callback();
}
2018-03-22 02:08:37 +08:00
this.pgConnection.getConnection(this.owner, (err, connection) => {
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-22 02:08:37 +08:00
this.affectedTablesCache.set(dbname, token, affectedTables);
callback(err, affectedTables);
});
});
});
};
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);
});
};