From 1737cbe1a53d83d7cdf717a63aa9a125c9d92713 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Wed, 8 Jul 2015 15:50:59 +0200 Subject: [PATCH] Unifies named map instantiation so it's easy to work on it --- lib/cartodb/controllers/named_maps.js | 230 ++++++++++++++++- lib/cartodb/controllers/named_static_maps.js | 247 ------------------- lib/cartodb/server.js | 24 +- 3 files changed, 234 insertions(+), 267 deletions(-) delete mode 100644 lib/cartodb/controllers/named_static_maps.js diff --git a/lib/cartodb/controllers/named_maps.js b/lib/cartodb/controllers/named_maps.js index 56aa8087..96da8a7b 100644 --- a/lib/cartodb/controllers/named_maps.js +++ b/lib/cartodb/controllers/named_maps.js @@ -5,15 +5,17 @@ var templateName = require('../backends/template_maps').templateName; var NamedMapsCacheEntry = require('../cache/model/named_maps_entry'); var cors = require('../middleware/cors'); -function NamedMapsController(app, templateMaps, metadataBackend, mapBackend, templateBaseUrl, surrogateKeysCache, - layergroupRequestDecorator) { +function NamedMapsController(app, templateMaps, metadataBackend, mapBackend, staticMapBackend, templateBaseUrl, + surrogateKeysCache, layergroupRequestDecorator, tablesExtentApi) { this.app = app; this.templateMaps = templateMaps; this.metadataBackend = metadataBackend; this.mapBackend = mapBackend; + this.staticMapBackend = staticMapBackend; this.templateBaseUrl = templateBaseUrl; this.surrogateKeysCache = surrogateKeysCache; this.layergroupRequestDecorator = layergroupRequestDecorator; + this.tablesExtentApi = tablesExtentApi; } module.exports = NamedMapsController; @@ -21,6 +23,9 @@ module.exports = NamedMapsController; NamedMapsController.prototype.register = function(app) { app.get(this.templateBaseUrl + '/: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.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)); }; @@ -305,3 +310,224 @@ function ifInvalidContentType(req, description) { throw new Error(description); } } + +NamedMapsController.prototype.staticMap = function(req, res) { + var self = this; + + var cdbUser = req.context.user; + + var format = req.params.format === 'jpg' ? 'jpeg' : 'png'; + + var template; + var layergroupConfig; + var layergroupId; + var params; + var cacheChannel; + + var layergroupDecorator = { + beforeLayergroupCreate: function(requestMapConfig, callback) { + self.layergroupRequestDecorator.beforeLayergroupCreate(req, requestMapConfig, callback); + }, + afterLayergroupCreate: function(layergroup, response, callback) { + self.layergroupRequestDecorator.afterLayergroupCreate(req, layergroup, response, callback); + } + }; + + step( + function reqParams() { + self.app.req2params(req, this); + }, + function getTemplate(err) { + assert.ifError(err); + self.templateMaps.getTemplate(cdbUser, templateName(req.params.template_id), this); + }, + function checkExists(err, tpl) { + assert.ifError(err); + if (!tpl) { + var notFoundErr = new Error( + "Template '" + templateName(req.params.template_id) + "' of user '" + cdbUser + "' not found" + ); + notFoundErr.http_status = 404; + throw notFoundErr; + } + return tpl; + }, + function checkAuthorized(err, tpl) { + assert.ifError(err); + + var authorized = false; + try { + authorized = self.templateMaps.isAuthorized(tpl, req.query.auth_token); + } catch (err) { + // we catch to add http_status + var authorizationFailedErr = new Error('Failed to authorize template'); + authorizationFailedErr.http_status = 403; + throw authorizationFailedErr; + } + if ( ! authorized ) { + var unauthorizedErr = new Error('Unauthorized template instantiation'); + unauthorizedErr.http_status = 403; + throw unauthorizedErr; + } + + return tpl; + }, + function prepareParams(err, tpl) { + assert.ifError(err); + + template = tpl; + + var templateParams = {}; + if (req.query.config) { + try { + templateParams = JSON.parse(req.query.config); + } catch (e) { + throw new Error('malformed config parameter, should be a valid JSON'); + } + } + + return templateParams; + }, + function instantiateTemplate(err, templateParams) { + assert.ifError(err); + return self.templateMaps.instance(template, templateParams); + }, + function prepareLayergroup(err, layergroup) { + assert.ifError(err); + layergroupConfig = layergroup; + params = { + user: req.params.user + }; + self.app.setDBParams(cdbUser, params, this); + }, + function setApiKey(err){ + assert.ifError(err); + self.mapBackend.createLayergroup(layergroupConfig, params, layergroupDecorator, this); + }, + function prepareResponse(err, layergroup) { + assert.ifError(err); + + // added by createLayergroup + cacheChannel = res.header('X-Cache-Channel'); + res.removeHeader('X-Cache-Channel'); + self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbUser, template.name)); + + layergroupId = layergroup.layergroupid.split(":")[0]; + + return null; + }, + function staticImageOptions(err) { + assert.ifError(err); + getStaticImageOptions(template, this); + }, + function estimateBounds(err, imageOpts) { + assert.ifError(err); + if (imageOpts) { + return imageOpts; + } + + var defaultZoomCenter = { + zoom: 1, + center: { + lng: 0, + lat: 0 + } + }; + + var dbTables = cacheChannel.split(':'); + if (dbTables.length <= 1 || dbTables[1].length === 0) { + return defaultZoomCenter; + } + + var tableNames = dbTables[1].split(','); + if (tableNames.length === 0) { + return defaultZoomCenter; + } + + var next = this; + self.tablesExtentApi.getBounds(cdbUser, tableNames, function(err, result) { + next(null, result || defaultZoomCenter); + }); + }, + function getImage(err, imageOpts) { + assert.ifError(err); + + var imageParams = _.extend({}, params, { + token: layergroupId, + format: req.params.format + }); + + var width = +req.params.width; + var height = +req.params.height; + + if (!_.isUndefined(imageOpts.zoom) && imageOpts.center) { + self.staticMapBackend.getImage(imageParams, width, height, imageOpts.zoom, imageOpts.center, this); + } else { + self.staticMapBackend.getImage(imageParams, width, height, imageOpts.bounds, this); + } + }, + function handleImage(err, image, headers, stats) { + if (req.profiler) { + req.profiler.done('render-' + format); + req.profiler.add(stats || {}); + } + + if (err) { + if (!err.error) { + err.error = err.message; + } + self.app.sendError(res, err, self.app.findStatusCode(err), 'STATIC_VIZ_MAP', err); + } else { + res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format); + res.setHeader('Cache-Control', 'public,max-age=7200,must-revalidate'); + self.app.sendResponse(res, [image, 200]); + } + } + ); +}; + +function getStaticImageOptions(template, callback) { + if (template.view) { + var zoomCenter = templateZoomCenter(template.view); + if (zoomCenter) { + return callback(null, zoomCenter); + } + + var bounds = templateBounds(template.view); + if (bounds) { + return callback(null, bounds); + } + } + return callback(null, null); +} + +function templateZoomCenter(view) { + if (!_.isUndefined(view.zoom) && view.center) { + return { + zoom: view.zoom, + center: view.center + }; + } + return false; +} + +function templateBounds(view) { + if (view.bounds) { + var hasAllBounds = _.every(['west', 'south', 'east', 'north'], function(prop) { + return !!view.bounds[prop]; + }); + if (hasAllBounds) { + return { + bounds: { + west: view.bounds.west, + south: view.bounds.south, + east: view.bounds.east, + north: view.bounds.north + } + }; + } else { + return false; + } + } + return false; +} diff --git a/lib/cartodb/controllers/named_static_maps.js b/lib/cartodb/controllers/named_static_maps.js deleted file mode 100644 index 06ac335e..00000000 --- a/lib/cartodb/controllers/named_static_maps.js +++ /dev/null @@ -1,247 +0,0 @@ -var step = require('step'); -var assert = require('assert'); -var templateName = require('../backends/template_maps').templateName; -var NamedMapsCacheEntry = require('../cache/model/named_maps_entry'); -var _ = require('underscore'); -var cors = require('../middleware/cors'); - -function NamedStaticMapsController(app, serverOptions, templateMaps, mapBackend, staticMapBackend, surrogateKeysCache, - tablesExtentApi, layergroupRequestDecorator) { - this.app = app; - this.serverOptions = serverOptions; - this.templateMaps = templateMaps; - this.mapBackend = mapBackend; - this.staticMapBackend = staticMapBackend; - this.surrogateKeysCache = surrogateKeysCache; - this.tablesExtentApi = tablesExtentApi; - this.layergroupRequestDecorator = layergroupRequestDecorator; -} - -module.exports = NamedStaticMapsController; - -NamedStaticMapsController.prototype.register = function(app) { - app.get( - app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format', cors(), this.named.bind(this) - ); -}; - -NamedStaticMapsController.prototype.named = function(req, res) { - var self = this; - - var cdbUser = req.context.user; - - var format = req.params.format === 'jpg' ? 'jpeg' : 'png'; - - var template; - var layergroupConfig; - var layergroupId; - var params; - var cacheChannel; - - var layergroupDecorator = { - beforeLayergroupCreate: function(requestMapConfig, callback) { - self.layergroupRequestDecorator.beforeLayergroupCreate(req, requestMapConfig, callback); - }, - afterLayergroupCreate: function(layergroup, response, callback) { - self.layergroupRequestDecorator.afterLayergroupCreate(req, layergroup, response, callback); - } - }; - - step( - function reqParams() { - self.app.req2params(req, this); - }, - function getTemplate(err) { - assert.ifError(err); - self.templateMaps.getTemplate(cdbUser, templateName(req.params.template_id), this); - }, - function checkExists(err, tpl) { - assert.ifError(err); - if (!tpl) { - var notFoundErr = new Error( - "Template '" + templateName(req.params.template_id) + "' of user '" + cdbUser + "' not found" - ); - notFoundErr.http_status = 404; - throw notFoundErr; - } - return tpl; - }, - function checkAuthorized(err, tpl) { - assert.ifError(err); - - var authorized = false; - try { - authorized = self.templateMaps.isAuthorized(tpl, req.query.auth_token); - } catch (err) { - // we catch to add http_status - var authorizationFailedErr = new Error('Failed to authorize template'); - authorizationFailedErr.http_status = 403; - throw authorizationFailedErr; - } - if ( ! authorized ) { - var unauthorizedErr = new Error('Unauthorized template instantiation'); - unauthorizedErr.http_status = 403; - throw unauthorizedErr; - } - - return tpl; - }, - function prepareParams(err, tpl) { - assert.ifError(err); - - template = tpl; - - var templateParams = {}; - if (req.query.config) { - try { - templateParams = JSON.parse(req.query.config); - } catch (e) { - throw new Error('malformed config parameter, should be a valid JSON'); - } - } - - return templateParams; - }, - function instantiateTemplate(err, templateParams) { - assert.ifError(err); - return self.templateMaps.instance(template, templateParams); - }, - function prepareLayergroup(err, layergroup) { - assert.ifError(err); - layergroupConfig = layergroup; - params = { - user: req.params.user - }; - self.app.setDBParams(cdbUser, params, this); - }, - function setApiKey(err){ - assert.ifError(err); - self.mapBackend.createLayergroup(layergroupConfig, params, layergroupDecorator, this); - }, - function prepareResponse(err, layergroup) { - assert.ifError(err); - - // added by createLayergroup - cacheChannel = res.header('X-Cache-Channel'); - res.removeHeader('X-Cache-Channel'); - self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbUser, template.name)); - - layergroupId = layergroup.layergroupid.split(":")[0]; - - return null; - }, - function staticImageOptions(err) { - assert.ifError(err); - getStaticImageOptions(template, this); - }, - function estimateBounds(err, imageOpts) { - assert.ifError(err); - if (imageOpts) { - return imageOpts; - } - - var defaultZoomCenter = { - zoom: 1, - center: { - lng: 0, - lat: 0 - } - }; - - var dbTables = cacheChannel.split(':'); - if (dbTables.length <= 1 || dbTables[1].length === 0) { - return defaultZoomCenter; - } - - var tableNames = dbTables[1].split(','); - if (tableNames.length === 0) { - return defaultZoomCenter; - } - - var next = this; - self.tablesExtentApi.getBounds(cdbUser, tableNames, function(err, result) { - next(null, result || defaultZoomCenter); - }); - }, - function getImage(err, imageOpts) { - assert.ifError(err); - - var imageParams = _.extend({}, params, { - token: layergroupId, - format: req.params.format - }); - - var width = +req.params.width; - var height = +req.params.height; - - if (!_.isUndefined(imageOpts.zoom) && imageOpts.center) { - self.staticMapBackend.getImage(imageParams, width, height, imageOpts.zoom, imageOpts.center, this); - } else { - self.staticMapBackend.getImage(imageParams, width, height, imageOpts.bounds, this); - } - }, - function handleImage(err, image, headers, stats) { - if (req.profiler) { - req.profiler.done('render-' + format); - req.profiler.add(stats || {}); - } - - if (err) { - if (!err.error) { - err.error = err.message; - } - self.app.sendError(res, err, self.app.findStatusCode(err), 'STATIC_VIZ_MAP', err); - } else { - res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format); - res.setHeader('Cache-Control', 'public,max-age=7200,must-revalidate'); - self.app.sendResponse(res, [image, 200]); - } - } - ); -}; - -function getStaticImageOptions(template, callback) { - if (template.view) { - var zoomCenter = templateZoomCenter(template.view); - if (zoomCenter) { - return callback(null, zoomCenter); - } - - var bounds = templateBounds(template.view); - if (bounds) { - return callback(null, bounds); - } - } - return callback(null, null); -} - -function templateZoomCenter(view) { - if (!_.isUndefined(view.zoom) && view.center) { - return { - zoom: view.zoom, - center: view.center - }; - } - return false; -} - -function templateBounds(view) { - if (view.bounds) { - var hasAllBounds = _.every(['west', 'south', 'east', 'north'], function(prop) { - return !!view.bounds[prop]; - }); - if (hasAllBounds) { - return { - bounds: { - west: view.bounds.west, - south: view.bounds.south, - east: view.bounds.east, - north: view.bounds.north - } - }; - } else { - return false; - } - } - return false; -} diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index c79ec2cf..cb40be53 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -318,15 +318,20 @@ module.exports = function(serverOptions) { var staticMapsController = new StaticMapsController(app, staticMapBackend); staticMapsController.register(app); + var TablesExtentApi = require('./api/tables_extent_api'); + var tablesExtentApi = new TablesExtentApi(pgQueryRunner); + var NamedMapsController = require('./controllers/named_maps'), namedMapsController = new NamedMapsController( app, templateMaps, metadataBackend, mapBackend, + staticMapBackend, template_baseurl, surrogateKeysCache, - layergroupRequestDecorator + layergroupRequestDecorator, + tablesExtentApi ); namedMapsController.register(app); @@ -334,23 +339,6 @@ module.exports = function(serverOptions) { namedMapsAdminController = new NamedMapsAdminController(app, templateMaps, template_baseurl); namedMapsAdminController.register(app); - var TablesExtentApi = require('./api/tables_extent_api'); - var tablesExtentApi = new TablesExtentApi(pgQueryRunner); - - var NamedStaticMapsController = require('./controllers/named_static_maps'); - var namedStaticMapsController = new NamedStaticMapsController( - app, - serverOptions, - templateMaps, - mapBackend, - staticMapBackend, - surrogateKeysCache, - tablesExtentApi, - layergroupRequestDecorator - ); - namedStaticMapsController.register(app); - - var healthCheck = new HealthCheck(); app.get('/health', function(req, res) { var healthConfig = global.environment.health || {};