Splits controllers and supports after layergroup creation actions

This commit is contained in:
Raul Ochoa 2015-07-10 11:24:32 +02:00
parent 6e0678e084
commit 9bece712a9
8 changed files with 436 additions and 402 deletions

View File

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

View File

@ -0,0 +1,205 @@
var assert = require('assert');
var step = require('step');
var cors = require('../middleware/cors');
var windshaft = require('windshaft');
var MapStoreMapConfigProvider = windshaft.model.provider.MapStoreMapConfig;
/**
* @param app
* @param {MapStore} mapStore
* @param {TileBackend} tileBackend
* @param {PreviewBackend} previewBackend
* @param {AttributesBackend} attributesBackend
* @constructor
*/
function LayergroupController(app, mapStore, tileBackend, previewBackend, attributesBackend) {
this.app = app;
this.mapStore = mapStore;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.attributesBackend = attributesBackend;
}
module.exports = LayergroupController;
LayergroupController.prototype.register = function(app) {
app.get(app.base_url_mapconfig + '/:token/:z/:x/:y@:scale_factor?x.:format', cors(), this.tile.bind(this));
app.get(app.base_url_mapconfig + '/:token/:z/:x/:y.:format', cors(), this.tile.bind(this));
app.get(app.base_url_mapconfig + '/:token/:layer/:z/:x/:y.(:format)', cors(), this.layer.bind(this));
app.get(app.base_url_mapconfig + '/:token/:layer/attributes/:fid', cors(), this.attributes.bind(this));
app.get(app.base_url_mapconfig + '/static/center/:token/:z/:lat/:lng/:width/:height.:format', cors(),
this.center.bind(this));
app.get(app.base_url_mapconfig + '/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format', cors(),
this.bbox.bind(this));
};
LayergroupController.prototype.attributes = function(req, res) {
var self = this;
req.profiler.start('windshaft.maplayer_attribute');
step(
function setupParams() {
self.app.req2params(req, this);
},
function retrieveFeatureAttributes(err) {
req.profiler.done('req2params');
assert.ifError(err);
self.attributesBackend.getFeatureAttributes(req.params, false, this);
},
function finish(err, tile, stats) {
req.profiler.add(stats || {});
if (err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
var statusCode = self.app.findStatusCode(err);
self.app.sendError(res, { errors: [errMsg] }, statusCode, 'GET ATTRIBUTES', err);
} else {
self.app.sendResponse(res, [tile, 200]);
}
}
);
};
// Gets a tile for a given token and set of tile ZXY coords. (OSM style)
LayergroupController.prototype.tile = function(req, res) {
req.profiler.start('windshaft.map_tile');
this.tileOrLayer(req, res);
};
// Gets a tile for a given token, layer set of tile ZXY coords. (OSM style)
LayergroupController.prototype.layer = function(req, res, next) {
if (req.params.token === 'static') {
return next();
}
req.profiler.start('windshaft.maplayer_tile');
this.tileOrLayer(req, res);
};
LayergroupController.prototype.tileOrLayer = function (req, res) {
var self = this;
step(
function mapController$prepareParams() {
self.app.req2params(req, this);
},
function mapController$getTileOrGrid(err) {
req.profiler.done('req2params');
if ( err ) {
throw err;
}
self.tileBackend.getTile(new MapStoreMapConfigProvider(self.mapStore, req.params), req.params, this);
},
function mapController$finalize(err, tile, headers, stats) {
req.profiler.add(stats);
self.finalizeGetTileOrGrid(err, req, res, tile, headers);
return null;
},
function finish(err) {
if ( err ) {
console.error("windshaft.tiles: " + err);
}
}
);
};
// This function is meant for being called as the very last
// step by all endpoints serving tiles or grids
LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers) {
var supportedFormats = {
grid_json: true,
json_torque: true,
torque_json: true,
png: true
};
var formatStat = 'invalid';
if (req.params.format) {
var format = req.params.format.replace('.', '_');
if (supportedFormats[format]) {
formatStat = format;
}
}
if (err){
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
var statusCode = this.app.findStatusCode(err);
// Rewrite mapnik parsing errors to start with layer number
var matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
if (matches) {
errMsg = 'style'+matches[2]+': ' + matches[1];
}
this.app.sendError(res, { errors: ['' + errMsg] }, statusCode, 'TILE RENDER', err);
global.statsClient.increment('windshaft.tiles.error');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.error');
} else {
this.app.sendWithHeaders(res, tile, 200, headers);
global.statsClient.increment('windshaft.tiles.success');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.success');
}
};
LayergroupController.prototype.bbox = function(req, res) {
this.staticMap(req, res, +req.params.width, +req.params.height, {
west: +req.params.west,
north: +req.params.north,
east: +req.params.east,
south: +req.params.south
});
};
LayergroupController.prototype.center = function(req, res) {
this.staticMap(req, res, +req.params.width, +req.params.height, +req.params.z, {
lng: +req.params.lng,
lat: +req.params.lat
});
};
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center) {
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
req.params.layer = 'all';
req.params.format = 'png';
var self = this;
step(
function() {
self.app.req2params(req, this);
},
function(err) {
req.profiler.done('req2params');
assert.ifError(err);
if (center) {
self.previewBackend.getImage(new MapStoreMapConfigProvider(self.mapStore, req.params),
format, width, height, zoom, center, this);
} else {
self.previewBackend.getImage(new MapStoreMapConfigProvider(self.mapStore, req.params),
format, width, height, zoom /* bounds */, this);
}
},
function handleImage(err, image, headers, stats) {
req.profiler.done('render-' + format);
req.profiler.add(stats || {});
if (err) {
if (!err.error) {
err.error = err.message;
}
self.app.sendError(res, {errors: ['' + err] }, self.app.findStatusCode(err), 'STATIC_MAP', err);
} else {
res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format);
self.app.sendResponse(res, [image, 200]);
}
}
);
};

View File

@ -1,37 +1,37 @@
var _ = require('underscore');
var assert = require('assert'); var assert = require('assert');
var step = require('step'); var step = require('step');
var windshaft = require('windshaft');
var cors = require('../middleware/cors'); var cors = require('../middleware/cors');
var windshaft = require('windshaft');
var MapStoreMapConfigProvider = windshaft.model.provider.MapStoreMapConfig;
var MapConfig = windshaft.model.MapConfig; var MapConfig = windshaft.model.MapConfig;
var Datasource = windshaft.model.Datasource; var Datasource = windshaft.model.Datasource;
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter'); var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter');
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
/** /**
* @param app * @param app
* @param {PgConnection} pgConnection * @param {PgConnection} pgConnection
* @param {MapStore} mapStore
* @param {TemplateMaps} templateMaps * @param {TemplateMaps} templateMaps
* @param {MapBackend} mapBackend * @param {MapBackend} mapBackend
* @param {TileBackend} tileBackend * @param metadataBackend
* @param {PreviewBackend} previewBackend * @param {QueryTablesApi} queryTablesApi
* @param {AttributesBackend} attributesBackend * @param {SurrogateKeysCache} surrogateKeysCache
* @constructor * @constructor
*/ */
function MapController(app, pgConnection, mapStore, templateMaps, mapBackend, tileBackend, previewBackend, function MapController(app, pgConnection, templateMaps, mapBackend, metadataBackend, queryTablesApi,
attributesBackend) { surrogateKeysCache) {
this._app = app; this.app = app;
this.pgConnection = pgConnection; this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.templateMaps = templateMaps; this.templateMaps = templateMaps;
this.mapBackend = mapBackend; this.mapBackend = mapBackend;
this.tileBackend = tileBackend; this.metadataBackend = metadataBackend;
this.previewBackend = previewBackend; this.queryTablesApi = queryTablesApi;
this.attributesBackend = attributesBackend; this.surrogateKeysCache = surrogateKeysCache;
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
} }
@ -39,57 +39,82 @@ module.exports = MapController;
MapController.prototype.register = function(app) { MapController.prototype.register = function(app) {
app.get(app.base_url_mapconfig + '/:token/:z/:x/:y@:scale_factor?x.:format', cors(), this.tile.bind(this));
app.get(app.base_url_mapconfig + '/:token/:z/:x/:y.:format', cors(), this.tile.bind(this));
app.get(app.base_url_mapconfig + '/:token/:layer/:z/:x/:y.(:format)', cors(), this.layer.bind(this));
app.options(app.base_url_mapconfig, cors('Content-Type'));
app.get(app.base_url_mapconfig, cors(), this.createGet.bind(this)); app.get(app.base_url_mapconfig, cors(), this.createGet.bind(this));
app.post(app.base_url_mapconfig, cors(), this.createPost.bind(this)); app.post(app.base_url_mapconfig, cors(), this.createPost.bind(this));
app.get(app.base_url_mapconfig + '/:token/:layer/attributes/:fid', cors(), this.attributes.bind(this)); app.get(app.base_url_templated + '/:template_id/jsonp', cors(), this.jsonp.bind(this));
app.get(app.base_url_mapconfig + '/static/center/:token/:z/:lat/:lng/:width/:height.:format', cors(), app.post(app.base_url_templated + '/:template_id', cors(), this.instantiate.bind(this));
this.center.bind(this)); app.options(app.base_url_mapconfig, cors('Content-Type'));
app.get(app.base_url_mapconfig + '/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format', cors(),
this.bbox.bind(this));
}; };
MapController.prototype.attributes = function(req, res) { MapController.prototype.createGet = function(req, res){
var self = this; req.profiler.start('windshaft.createmap_get');
req.profiler.start('windshaft.maplayer_attribute'); this.create(req, res, function createGet$prepareConfig(err, req) {
assert.ifError(err);
if ( ! req.params.config ) {
throw new Error('layergroup GET needs a "config" parameter');
}
return JSON.parse(req.params.config);
});
};
step( MapController.prototype.createPost = function(req, res) {
function setupParams() { req.profiler.start('windshaft.createmap_post');
self._app.req2params(req, this);
},
function retrieveFeatureAttributes(err) {
req.profiler.done('req2params');
assert.ifError(err); this.create(req, res, function createPost$prepareConfig(err, req) {
assert.ifError(err);
if (!req.is('application/json')) {
throw new Error('layergroup POST data must be of type application/json');
}
return req.body;
});
};
self.attributesBackend.getFeatureAttributes(req.params, false, this); MapController.prototype.instantiate = function(req, res) {
}, if (req.profiler) {
function finish(err, tile, stats) { req.profiler.start('windshaft-cartodb.instance_template_post');
req.profiler.add(stats || {}); }
if (err) { this.instantiateTemplate(req, res, function prepareTemplateParams(callback) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68 if (!req.is('application/json')) {
var errMsg = err.message ? ( '' + err.message ) : ( '' + err ); return callback(new Error('Template POST data must be of type application/json'));
var statusCode = self._app.findStatusCode(err); }
self._app.sendError(res, { errors: [errMsg] }, statusCode, 'GET ATTRIBUTES', err); return callback(null, req.body);
} else { });
self._app.sendResponse(res, [tile, 200]); };
MapController.prototype.jsonp = function(req, res) {
if (req.profiler) {
req.profiler.start('windshaft-cartodb.instance_template_get');
}
this.instantiateTemplate(req, res, function prepareJsonTemplateParams(callback) {
var err = null;
if ( req.query.callback === undefined || req.query.callback.length === 0) {
err = new Error('callback parameter should be present and be a function name');
}
var templateParams = {};
if (req.query.config) {
try {
templateParams = JSON.parse(req.query.config);
} catch(e) {
err = new Error('Invalid config parameter, should be a valid JSON');
} }
} }
);
return callback(err, templateParams);
});
}; };
MapController.prototype.create = function(req, res, prepareConfigFn) { MapController.prototype.create = function(req, res, prepareConfigFn) {
var self = this; var self = this;
var mapConfig;
step( step(
function setupParams(){ function setupParams(){
self._app.req2params(req, this); self.app.req2params(req, this);
}, },
prepareConfigFn, prepareConfigFn,
function beforeLayergroupCreate(err, requestMapConfig) { function beforeLayergroupCreate(err, requestMapConfig) {
@ -110,178 +135,151 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
}, },
function createLayergroup(err, requestMapConfig, datasource) { function createLayergroup(err, requestMapConfig, datasource) {
assert.ifError(err); assert.ifError(err);
self.mapBackend.createLayergroup( mapConfig = new MapConfig(requestMapConfig, datasource || Datasource.EmptyDatasource());
new MapConfig(requestMapConfig, datasource || Datasource.EmptyDatasource()), req.params, this self.mapBackend.createLayergroup(mapConfig, req.params, this);
); },
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, mapConfig, layergroup, this);
}, },
function finish(err, layergroup) { function finish(err, layergroup) {
if (err) { if (err) {
var statusCode = self._app.findStatusCode(err); var statusCode = self.app.findStatusCode(err);
self._app.sendError(res, { errors: [ err.message ] }, statusCode, 'ANONYMOUS LAYERGROUP', err); self.app.sendError(res, { errors: [ err.message ] }, statusCode, 'ANONYMOUS LAYERGROUP', err);
} else { } else {
self._app.sendResponse(res, [layergroup, 200]); self.app.sendResponse(res, [layergroup, 200]);
} }
} }
); );
}; };
MapController.prototype.createGet = function(req, res){ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn) {
req.profiler.start('windshaft.createmap_get');
this.create(req, res, function createGet$prepareConfig(err, req) {
assert.ifError(err);
if ( ! req.params.config ) {
throw new Error('layergroup GET needs a "config" parameter');
}
return JSON.parse(req.params.config);
});
};
// TODO rewrite this so it is possible to share code with `MapController::create` method
MapController.prototype.createPost = function(req, res) {
req.profiler.start('windshaft.createmap_post');
this.create(req, res, function createPost$prepareConfig(err, req) {
assert.ifError(err);
if (!req.is('application/json')) {
throw new Error('layergroup POST data must be of type application/json');
}
return req.body;
});
};
// Gets a tile for a given token and set of tile ZXY coords. (OSM style)
MapController.prototype.tile = function(req, res) {
req.profiler.start('windshaft.map_tile');
this.tileOrLayer(req, res);
};
// Gets a tile for a given token, layer set of tile ZXY coords. (OSM style)
MapController.prototype.layer = function(req, res, next) {
if (req.params.token === 'static') {
return next();
}
req.profiler.start('windshaft.maplayer_tile');
this.tileOrLayer(req, res);
};
MapController.prototype.tileOrLayer = function (req, res) {
var self = this; var self = this;
var cdbuser = req.context.user;
var mapConfigProvider;
var mapConfig;
step( step(
function mapController$prepareParams() { function getTemplateParams() {
self._app.req2params(req, this); prepareParamsFn(this);
}, },
function mapController$getTileOrGrid(err) { function getTemplate(err, templateParams) {
req.profiler.done('req2params'); assert.ifError(err);
if ( err ) { mapConfigProvider = new NamedMapMapConfigProvider(
throw err; self.templateMaps,
self.pgConnection,
cdbuser,
req.params.template_id,
templateParams,
req.query.auth_token,
req.params
);
mapConfigProvider.getMapConfig(this);
},
function createLayergroup(err, mapConfig_, rendererParams/*, context*/) {
assert.ifError(err);
mapConfig = mapConfig_;
self.mapBackend.createLayergroup(mapConfig, rendererParams, this);
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, mapConfig, layergroup, this);
},
function finishTemplateInstantiation(err, layergroup) {
if (err) {
var statusCode = self.app.findStatusCode(err);
self.app.sendError(res, { errors: [ err.message ] }, statusCode, 'NAMED MAP LAYERGROUP', err);
} else {
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
res.header('X-Layergroup-Id', layergroup.layergroupid);
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
self.app.sendResponse(res, [layergroup, 200]);
} }
self.tileBackend.getTile(new MapStoreMapConfigProvider(self.mapStore, req.params), req.params, this); }
);
};
MapController.prototype.afterLayergroupCreate = function(req, mapconfig, layergroup, callback) {
var self = this;
var username = req.context.user;
var tasksleft = 2; // redis key and affectedTables
var errors = [];
var done = function(err) {
if ( err ) {
errors.push('' + err);
}
if ( ! --tasksleft ) {
err = errors.length ? new Error(errors.join('\n')) : null;
callback(err, layergroup);
}
};
// 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);
// Don't wait for the mapview count increment to
// take place before proceeding. Error will be logged
// asynchronously
this.metadataBackend.incMapviewCount(username, mapconfig.obj().stat_tag, function(err) {
if (req.profiler) {
req.profiler.done('incMapviewCount');
}
if ( err ) {
console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
}
done();
});
var sql = mapconfig.getLayers().map(function(layer) {
return layer.options.sql;
}).join(';');
var dbName = req.params.dbname;
var cacheKey = dbName + ':' + layergroup.layergroupid;
step(
function getAffectedTablesAndLastUpdatedTime() {
self.queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
}, },
function mapController$finalize(err, tile, headers, stats) { function handleAffectedTablesAndLastUpdatedTime(err, result) {
req.profiler.add(stats); if (req.profiler) {
self.finalizeGetTileOrGrid(err, req, res, tile, headers); req.profiler.done('queryTablesAndLastUpdated');
}
assert.ifError(err);
var cacheChannel = self.app.buildCacheChannel(dbName, result.affectedTables);
self.app.channelCache[cacheKey] = cacheChannel;
// last update for layergroup cache buster
layergroup.layergroupid = layergroup.layergroupid + ':' + result.lastUpdatedTime;
layergroup.last_updated = new Date(result.lastUpdatedTime).toISOString();
var res = req.res;
if (res) {
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
res.header('Last-Modified', (new Date()).toUTCString());
res.header('X-Cache-Channel', cacheChannel);
}
res.header('X-Layergroup-Id', layergroup.layergroupid);
}
return null; return null;
}, },
function finish(err) { function finish(err) {
if ( err ) { done(err);
console.error("windshaft.tiles: " + err);
}
}
);
};
// This function is meant for being called as the very last
// step by all endpoints serving tiles or grids
MapController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers) {
var supportedFormats = {
grid_json: true,
json_torque: true,
torque_json: true,
png: true
};
var formatStat = 'invalid';
if (req.params.format) {
var format = req.params.format.replace('.', '_');
if (supportedFormats[format]) {
formatStat = format;
}
}
if (err){
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
var statusCode = this._app.findStatusCode(err);
// Rewrite mapnik parsing errors to start with layer number
var matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
if (matches) {
errMsg = 'style'+matches[2]+': ' + matches[1];
}
this._app.sendError(res, { errors: ['' + errMsg] }, statusCode, 'TILE RENDER', err);
global.statsClient.increment('windshaft.tiles.error');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.error');
} else {
this._app.sendWithHeaders(res, tile, 200, headers);
global.statsClient.increment('windshaft.tiles.success');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.success');
}
};
MapController.prototype.bbox = function(req, res) {
this.staticMap(req, res, +req.params.width, +req.params.height, {
west: +req.params.west,
north: +req.params.north,
east: +req.params.east,
south: +req.params.south
});
};
MapController.prototype.center = function(req, res) {
this.staticMap(req, res, +req.params.width, +req.params.height, +req.params.z, {
lng: +req.params.lng,
lat: +req.params.lat
});
};
MapController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center) {
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
req.params.layer = 'all';
req.params.format = 'png';
var self = this;
step(
function() {
self._app.req2params(req, this);
},
function(err) {
req.profiler.done('req2params');
assert.ifError(err);
if (center) {
self.previewBackend.getImage(new MapStoreMapConfigProvider(self.mapStore, req.params),
format, width, height, zoom, center, this);
} else {
self.previewBackend.getImage(new MapStoreMapConfigProvider(self.mapStore, req.params),
format, width, height, zoom /* bounds */, this);
}
},
function handleImage(err, image, headers, stats) {
req.profiler.done('render-' + format);
req.profiler.add(stats || {});
if (err) {
if (!err.error) {
err.error = err.message;
}
self._app.sendError(res, {errors: ['' + err] }, self._app.findStatusCode(err), 'STATIC_MAP', err);
} else {
res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format);
self._app.sendResponse(res, [image, 200]);
}
} }
); );
}; };

View File

@ -6,17 +6,13 @@ var cors = require('../middleware/cors');
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider'); var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
function NamedMapsController(app, pgConnection, mapStore, templateMaps, metadataBackend, mapBackend, tileBackend, function NamedMapsController(app, pgConnection, templateMaps, tileBackend, previewBackend, surrogateKeysCache,
previewBackend, templateBaseUrl, surrogateKeysCache, tablesExtentApi) { tablesExtentApi) {
this.app = app; this.app = app;
this.mapStore = mapStore;
this.pgConnection = pgConnection; this.pgConnection = pgConnection;
this.templateMaps = templateMaps; this.templateMaps = templateMaps;
this.metadataBackend = metadataBackend;
this.mapBackend = mapBackend;
this.tileBackend = tileBackend; this.tileBackend = tileBackend;
this.previewBackend = previewBackend; this.previewBackend = previewBackend;
this.templateBaseUrl = templateBaseUrl;
this.surrogateKeysCache = surrogateKeysCache; this.surrogateKeysCache = surrogateKeysCache;
this.tablesExtentApi = tablesExtentApi; this.tablesExtentApi = tablesExtentApi;
} }
@ -24,49 +20,10 @@ function NamedMapsController(app, pgConnection, mapStore, templateMaps, metadata
module.exports = NamedMapsController; module.exports = NamedMapsController;
NamedMapsController.prototype.register = function(app) { NamedMapsController.prototype.register = function(app) {
app.get(this.templateBaseUrl + '/:template_id/:layer/:z/:x/:y.(:format)', cors(), this.tile.bind(this)); app.get(app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)', cors(), this.tile.bind(this));
app.get(this.templateBaseUrl + '/:template_id/jsonp', cors(), this.jsonp.bind(this));
app.get( app.get(
app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format', cors(), this.staticMap.bind(this) app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format', cors(), this.staticMap.bind(this)
); );
app.post(this.templateBaseUrl + '/:template_id', cors(), this.instantiate.bind(this));
};
NamedMapsController.prototype.instantiate = function(req, res) {
if (req.profiler) {
req.profiler.start('windshaft-cartodb.instance_template_post');
}
this.instantiateTemplate(req, res, function prepareTemplateParams(callback) {
if (!req.is('application/json')) {
return callback(new Error('Template POST data must be of type application/json'));
}
return callback(null, req.body);
});
};
NamedMapsController.prototype.jsonp = function(req, res) {
if (req.profiler) {
req.profiler.start('windshaft-cartodb.instance_template_get');
}
this.instantiateTemplate(req, res, function prepareJsonTemplateParams(callback) {
var err = null;
if ( req.query.callback === undefined || req.query.callback.length === 0) {
err = new Error('callback parameter should be present and be a function name');
}
var templateParams = {};
if (req.query.config) {
try {
templateParams = JSON.parse(req.query.config);
} catch(e) {
err = new Error('Invalid config parameter, should be a valid JSON');
}
}
return callback(err, templateParams);
});
}; };
NamedMapsController.prototype.tile = function(req, res) { NamedMapsController.prototype.tile = function(req, res) {
@ -207,53 +164,6 @@ NamedMapsController.prototype.staticMap = function(req, res) {
); );
}; };
// Instantiate a template
NamedMapsController.prototype.instantiateTemplate = function(req, res, prepareParamsFn) {
var self = this;
var cdbuser = req.context.user;
var mapConfigProvider;
step(
function getTemplateParams() {
prepareParamsFn(this);
},
function getTemplate(err, templateParams) {
assert.ifError(err);
mapConfigProvider = new NamedMapMapConfigProvider(
this.templateMaps,
this.pgConnection,
cdbuser,
req.params.template_id,
templateParams,
req.query.auth_token,
req.params
);
mapConfigProvider.getMapConfig(this);
},
function createLayergroup(err, mapConfig, rendererParams/*, context*/) {
assert.ifError(err);
self.mapBackend.createLayergroup(mapConfig, rendererParams, this);
},
function finishTemplateInstantiation(err, layergroup) {
if (err) {
var statusCode = this._app.findStatusCode(err);
this.app.sendError(res, { errors: [ err.message ] }, statusCode, 'NAMED MAP LAYERGROUP', err);
} else {
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
res.header('X-Layergroup-Id', layergroup.layergroupid);
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
this.app.sendResponse(res, [layergroup, 200]);
}
}
);
};
function getStaticImageOptions(template, callback) { function getStaticImageOptions(template, callback) {
if (template.view) { if (template.view) {
var zoomCenter = templateZoomCenter(template.view); var zoomCenter = templateZoomCenter(template.view);

View File

@ -5,21 +5,20 @@ var templateName = require('../backends/template_maps').templateName;
var cors = require('../middleware/cors'); var cors = require('../middleware/cors');
function NamedMapsAdminController(app, templateMaps, templateBaseUrl) { function NamedMapsAdminController(app, templateMaps) {
this.app = app; this.app = app;
this.templateMaps = templateMaps; this.templateMaps = templateMaps;
this.templateBaseUrl = templateBaseUrl;
} }
module.exports = NamedMapsAdminController; module.exports = NamedMapsAdminController;
NamedMapsAdminController.prototype.register = function(app) { NamedMapsAdminController.prototype.register = function(app) {
app.post(this.templateBaseUrl, cors(), this.create.bind(this)); app.post(app.base_url_templated, cors(), this.create.bind(this));
app.put(this.templateBaseUrl + '/:template_id', cors(), this.update.bind(this)); app.put(app.base_url_templated + '/:template_id', cors(), this.update.bind(this));
app.get(this.templateBaseUrl + '/:template_id', cors(), this.retrieve.bind(this)); app.get(app.base_url_templated + '/:template_id', cors(), this.retrieve.bind(this));
app.del(this.templateBaseUrl + '/:template_id', cors(), this.destroy.bind(this)); app.del(app.base_url_templated + '/:template_id', cors(), this.destroy.bind(this));
app.get(this.templateBaseUrl, cors(), this.list.bind(this)); app.get(app.base_url_templated, cors(), this.list.bind(this));
app.options(this.templateBaseUrl + '/:template_id', cors('Content-Type')); app.options(app.base_url_templated + '/:template_id', cors('Content-Type'));
}; };
NamedMapsAdminController.prototype.create = function(req, res) { NamedMapsAdminController.prototype.create = function(req, res) {

View File

@ -195,90 +195,6 @@ module.exports = function(serverOptions) {
return statusCode; return statusCode;
}; };
// var layergroupRequestDecorator = {
// afterLayergroupCreate: function(req, mapconfig, layergroup, callback) {
// var token = layergroup.layergroupid;
//
// var username = req.context.user;
//
// var tasksleft = 2; // redis key and affectedTables
// var errors = [];
//
// var done = function(err) {
// if ( err ) {
// errors.push('' + err);
// }
// if ( ! --tasksleft ) {
// err = errors.length ? new Error(errors.join('\n')) : null;
// callback(err);
// }
// };
//
// // 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
// var serverMetadata = global.environment.serverMetadata;
// if (serverMetadata) {
// _.extend(layergroup, serverMetadata);
// }
//
// // Don't wait for the mapview count increment to
// // take place before proceeding. Error will be logged
// // asyncronously
// metadataBackend.incMapviewCount(username, mapconfig.stat_tag, function(err) {
// if (req.profiler) {
// req.profiler.done('incMapviewCount');
// }
// if ( err ) {
// console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
// }
// done();
// });
//
// var sql = mapconfig.layers.map(function(layer) {
// return layer.options.sql;
// }).join(';');
//
// var dbName = req.params.dbname;
// var cacheKey = dbName + ':' + token;
//
// step(
// function getAffectedTablesAndLastUpdatedTime() {
// queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
// },
// function handleAffectedTablesAndLastUpdatedTime(err, result) {
// if (req.profiler) {
// req.profiler.done('queryTablesAndLastUpdated');
// }
// assert.ifError(err);
// var cacheChannel = app.buildCacheChannel(dbName, result.affectedTables);
// app.channelCache[cacheKey] = cacheChannel;
//
// // last update for layergroup cache buster
// layergroup.layergroupid = layergroup.layergroupid + ':' + result.lastUpdatedTime;
// layergroup.last_updated = new Date(result.lastUpdatedTime).toISOString();
//
// var res = req.res;
// if (res) {
// if (req.method === 'GET') {
// var ttl = global.environment.varnish.layergroupTtl || 86400;
// res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
// res.header('Last-Modified', (new Date()).toUTCString());
// res.header('X-Cache-Channel', cacheChannel);
// }
//
// res.header('X-Layergroup-Id', layergroup.layergroupid);
// }
//
// return null;
// },
// function finish(err) {
// done(err);
// }
// );
// }
// };
var TablesExtentApi = require('./api/tables_extent_api'); var TablesExtentApi = require('./api/tables_extent_api');
var tablesExtentApi = new TablesExtentApi(pgQueryRunner); var tablesExtentApi = new TablesExtentApi(pgQueryRunner);
@ -291,32 +207,35 @@ module.exports = function(serverOptions) {
next(); next();
}); });
new controller.Map( new controller.Layergroup(
app, app,
pgConnection,
mapStore, mapStore,
templateMaps,
mapBackend,
tileBackend, tileBackend,
previewBackend, previewBackend,
attributesBackend attributesBackend
).register(app); ).register(app);
new controller.Map(
app,
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
queryTablesApi,
surrogateKeysCache
).register(app);
new controller.NamedMaps( new controller.NamedMaps(
app, app,
pgConnection, pgConnection,
mapStore,
templateMaps, templateMaps,
metadataBackend,
mapBackend,
tileBackend, tileBackend,
previewBackend, previewBackend,
template_baseurl,
surrogateKeysCache, surrogateKeysCache,
tablesExtentApi tablesExtentApi
).register(app); ).register(app);
new controller.NamedMapsAdmin(app, templateMaps, template_baseurl).register(app); new controller.NamedMapsAdmin(app, templateMaps).register(app);
new controller.ServerInfo().register(app); new controller.ServerInfo().register(app);
@ -857,8 +776,8 @@ module.exports = function(serverOptions) {
}; };
function validateOptions(opts) { function validateOptions(opts) {
if (!_.isString(opts.base_url) || !_.isString(opts.base_url_mapconfig)) { if (!_.isString(opts.base_url) || !_.isString(opts.base_url_mapconfig) || !_.isString(opts.base_url_templated)) {
throw new Error("Must initialise Windshaft with: 'base_url'/'base_url_mapconfig' URLs"); throw new Error("Must initialise server with: 'base_url'/'base_url_mapconfig'/'base_url_templated' URLs");
} }
// Be nice and warn if configured mapnik version is != instaled mapnik version // Be nice and warn if configured mapnik version is != instaled mapnik version

View File

@ -42,6 +42,8 @@ module.exports = {
// //
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)', base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
base_url_templated: global.environment.base_url_templated || '(?:/maps/named|/tiles/template)',
grainstore: { grainstore: {
map: { map: {
// TODO: allow to specify in configuration // TODO: allow to specify in configuration

View File

@ -5,7 +5,7 @@ var cartodbServer = require('../../../../lib/cartodb/server');
var serverOptions = require('../../../../lib/cartodb/server_options'); var serverOptions = require('../../../../lib/cartodb/server_options');
var StatsClient = require('windshaft').stats.Client; var StatsClient = require('windshaft').stats.Client;
var MapController = require('../../../../lib/cartodb/controllers/map'); var LayergroupController = require('../../../../lib/cartodb/controllers/layergroup');
describe('tile stats', function() { describe('tile stats', function() {
@ -31,14 +31,14 @@ describe('tile stats', function() {
var ws = cartodbServer(serverOptions); var ws = cartodbServer(serverOptions);
ws.sendError = function(){}; ws.sendError = function(){};
var mapController = new MapController(ws, null); var layergroupController = new LayergroupController(ws, null);
var reqMock = { var reqMock = {
params: { params: {
format: invalidFormat format: invalidFormat
} }
}; };
mapController.finalizeGetTileOrGrid('Unsupported format png2', reqMock, {}, null, null); layergroupController.finalizeGetTileOrGrid('Unsupported format png2', reqMock, {}, null, null);
assert.ok(formatMatched, 'Format was never matched in increment method'); assert.ok(formatMatched, 'Format was never matched in increment method');
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method'); assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');
@ -64,9 +64,9 @@ describe('tile stats', function() {
var ws = cartodbServer(serverOptions); var ws = cartodbServer(serverOptions);
ws.sendError = function(){}; ws.sendError = function(){};
var mapController = new MapController(ws, null); var layergroupController = new LayergroupController(ws, null);
mapController.finalizeGetTileOrGrid('Another error happened', reqMock, {}, null, null); layergroupController.finalizeGetTileOrGrid('Another error happened', reqMock, {}, null, null);
assert.ok(formatMatched, 'Format was never matched in increment method'); assert.ok(formatMatched, 'Format was never matched in increment method');
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method'); assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');