<commit_before>var assert = require('assert');
var crypto = require('crypto');
var debug = require('debug')('windshaft:templates');
var step = require('step');
var _ = require('underscore');
var dot = require('dot');

var EventEmitter = require('events').EventEmitter;
var util = require('util');

// Class handling map templates
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps
//
// @param redis_pool an instance of a "redis-mpool"
//        See https://github.com/CartoDB/node-redis-mpool
//        Needs version 0.x.x of the API.
//
// @param opts TemplateMap options. Supported elements:
//    'max_user_templates' limit on the number of per-user
//
//
function TemplateMaps(redis_pool, opts) {
  if (!(this instanceof TemplateMaps)) {
      return new TemplateMaps();
  }

  EventEmitter.call(this);

  this.redis_pool = redis_pool;
  this.opts = opts || {};

  // Database containing templates
  // TODO: allow configuring ?
  // NOTE: currently it is the same as
  //       the one containing layergroups
  this.db_signatures = 0;

  //
  // Map templates are owned by a user that specifies access permissions
  // for their instances.
  //
  // We have the following datastores:
  //
  //  1. User templates: set of per-user map templates

  // User templates (HASH:tpl_id->tpl_val)
  this.key_usr_tpl = dot.template("map_tpl|{{=it.owner}}");
}

util.inherits(TemplateMaps, EventEmitter);

module.exports = TemplateMaps;

var o = TemplateMaps.prototype;

//--------------- PRIVATE METHODS --------------------------------

o._userTemplateLimit = function() {
  return this.opts.max_user_templates || 0;
};

/**
 * Internal function to communicate with redis
 *
 * @param redisFunc - the redis function to execute
 * @param redisArgs - the arguments for the redis function in an array
 * @param callback - function to pass results too.
 */
o._redisCmd = function(redisFunc, redisArgs, callback) {
  var redisClient;
  var that = this;
  var db = that.db_signatures;

  step(
    function getRedisClient() {
      that.redis_pool.acquire(db, this);
    },
    function executeQuery(err, data) {
      assert.ifError(err);
      redisClient = data;
      redisArgs.push(this);
      redisClient[redisFunc.toUpperCase()].apply(redisClient, redisArgs);
    },
    function releaseRedisClient(err, data) {
      if ( ! _.isUndefined(redisClient) ) {
          that.redis_pool.release(db, redisClient);
      }
      callback(err, data);
    }
  );
};

var _reValidNameIdentifier = /^[a-z0-9][0-9a-z_\-]*$/i;
var _reValidPlaceholderIdentifier = /^[a-z][0-9a-z_]*$/i;
// jshint maxcomplexity:15
o._checkInvalidTemplate = function(template) {
  if ( template.version !== '0.0.1' ) {
    return new Error("Unsupported template version " + template.version);
  }
  var tplname = template.name;
  if ( ! tplname ) {
    return new Error("Missing template name");
  }
  if ( ! tplname.match(_reValidNameIdentifier) ) {
    return new Error("Invalid characters in template name '" + tplname + "'");
  }

  var invalidError = isInvalidLayergroup(template.layergroup);
  if (invalidError) {
    return invalidError;
  }

  var placeholders = template.placeholders || {};

  var placeholderKeys = Object.keys(placeholders);
  for (var i = 0, len = placeholderKeys.length; i < len; i++) {
      var placeholderKey = placeholderKeys[i];

      if (!placeholderKey.match(_reValidPlaceholderIdentifier)) {
          return new Error("Invalid characters in placeholder name '" + placeholderKey + "'");
      }
      if ( ! placeholders[placeholderKey].hasOwnProperty('default') ) {
          return new Error("Missing default for placeholder '" + placeholderKey + "'");
      }
      if ( ! placeholders[placeholderKey].hasOwnProperty('type') ) {
          return new Error("Missing type for placeholder '" + placeholderKey + "'");
      }
  }

    var auth = template.auth || {};

    switch ( auth.method ) {
        case 'open':
            break;
        case 'token':
            if ( ! _.isArray(auth.valid_tokens) ) {
                return new Error("Invalid 'token' authentication: missing valid_tokens");
            }
            if ( ! auth.valid_tokens.length ) {
                return new Error("Invalid 'token' authentication: no valid_tokens");
            }
            break;
        default:
            return new Error("Unsupported authentication method: " + auth.method);
    }

    return false;
};

function isInvalidLayergroup(layergroup) {
    if (!layergroup) {
        return new Error('Missing layergroup');
    }

    var layers = layergroup.layers;

    if (!_.isArray(layers) || layers.length === 0) {
        return new Error('Missing or empty layers array from layergroup config');
    }

    var invalidLayers = layers
        .map(function(layer, layerIndex) {
            return layer.options ? null : layerIndex;
        })
        .filter(function(layerIndex) {
            return layerIndex !== null;
        });

    if (invalidLayers.length) {
        return new Error('Missing `options` in layergroup config for layers: ' + invalidLayers.join(', '));
    }

    return false;
}

function templateDefaults(template) {
    var templateAuth = _.defaults({}, template.auth || {}, {
        method: 'open'
    });
    return _.defaults({ auth: templateAuth }, template, {
        placeholders: {}
    });
}

//--------------- PUBLIC API -------------------------------------

// Add a template
//
// NOTE: locks user+template_name or fails
//
// @param owner cartodb username of the template owner
//
// @param template layergroup template, see
//        http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
//
// @param callback function(err, tpl_id)
//        Return template identifier (only valid for given user)
//
o.addTemplate = function(owner, template, callback) {
    var self = this;

    template = templateDefaults(template);

    var invalidError = this._checkInvalidTemplate(template);
    if ( invalidError ) {
        return callback(invalidError);
    }

    var templateName = template.name;
    var userTemplatesKey = this.key_usr_tpl({ owner:owner });
    var limit = this._userTemplateLimit();

    step(
        function checkLimit() {
            if ( ! limit ) {
                return 0;
            }
            self._redisCmd('HLEN', [ userTemplatesKey ], this);
        },
        function installTemplateIfDoesNotExist(err, numberOfTemplates) {
            assert.ifError(err);
            if ( limit && numberOfTemplates >= limit ) {
                var limitReachedError = new Error("User '" + owner + "' reached limit on number of templates (" +
                    numberOfTemplates + "/" + limit + ")");
                limitReachedError.http_status = 409;
                throw limitReachedError;
            }
            self._redisCmd('HSETNX', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
        },
        function validateInstallation(err, wasSet) {
            assert.ifError(err);
            if ( ! wasSet ) {
                throw new Error("Template '" + templateName + "' of user '" + owner + "' already exists");
            }

            return true;
        },
        function finish(err) {
            if (!err) {
                self.emit('add', owner, templateName, template);
            }

            callback(err, templateName, template);
        }
  );
};

// Delete a template
//
// @param owner cartodb username of the template owner
//
// @param tpl_id template identifier as returned
//        by addTemplate or listTemplates
//
// @param callback function(err)
//
o.delTemplate = function(owner, tpl_id, callback) {
    var self = this;
    step(
        function deleteTemplate() {
            self._redisCmd('HDEL', [ self.key_usr_tpl({ owner:owner }), tpl_id ], this);
        },
        function handleDeletion(err, deleted) {
            assert.ifError(err);
            if (!deleted) {
                throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
            }
            return true;
        },
        function finish(err) {
            if (!err) {
                self.emit('delete', owner, tpl_id);
            }

            callback(err);
        }
    );
};

// Update a template
//
// NOTE: locks user+template_name or fails
//
// Also deletes and re-creates associated authentication certificate,
// which in turn deletes all instance signatures
//
// @param owner cartodb username of the template owner
//
// @param tpl_id template identifier as returned by addTemplate
//
// @param template layergroup template, see
//        http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
//
// @param callback function(err)
//
o.updTemplate = function(owner, tpl_id, template, callback) {
    var self = this;

    template = templateDefaults(template);

    var invalidError = this._checkInvalidTemplate(template);

    if ( invalidError ) {
        return callback(invalidError);
    }

    var templateName = template.name;

    if ( tpl_id !== templateName ) {
        return callback(new Error("Cannot update name of a map template ('" + tpl_id + "' != '" + templateName + "')"));
    }

    var userTemplatesKey = this.key_usr_tpl({ owner:owner });

    var previousTemplate = null;

    step(
        function getExistingTemplate() {
            self._redisCmd('HGET', [ userTemplatesKey, tpl_id ], this);
        },
        function updateTemplate(err, _currentTemplate) {
            assert.ifError(err);
            if (!_currentTemplate) {
                throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
            }
            previousTemplate = _currentTemplate;
            self._redisCmd('HSET', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
        },
        function handleTemplateUpdate(err, didSetNewField) {
            assert.ifError(err);
            if (didSetNewField) {
                debug('New template created on update operation');
            }
            return true;
        },
        function finish(err) {
            if (!err) {
                if (self.fingerPrint(JSON.parse(previousTemplate)) !== self.fingerPrint(template)) {
                    self.emit('update', owner, templateName, template);
                }
            }

            callback(err, template);
        }
    );
};

// List user templates
//
// @param owner cartodb username of the templates owner
//
// @param callback function(err, tpl_id_list)
//        Returns a list of template identifiers
//
o.listTemplates = function(owner, callback) {
    this._redisCmd('HKEYS', [ this.key_usr_tpl({owner:owner}) ], callback);
};

// Get a templates
//
// @param owner cartodb username of the template owner
//
// @param tpl_id template identifier as returned
//        by addTemplate or listTemplates
//
// @param callback function(err, template)
//        Return full template definition
//
o.getTemplate = function(owner, tpl_id, callback) {
    var self = this;
    step(
        function getTemplate() {
            self._redisCmd('HGET', [ self.key_usr_tpl({owner:owner}), tpl_id ], this);
        },
        function parseTemplate(err, tpl_val) {
            assert.ifError(err);
            return JSON.parse(tpl_val);
        },
        function finish(err, tpl) {
            callback(err, tpl);
        }
    );
};

o.isAuthorized = function(template, authTokens) {
    if (!template) {
        return false;
    }

    authTokens = _.isArray(authTokens) ? authTokens : [authTokens];

    var templateAuth = template.auth;

    if (!templateAuth) {
        return false;
    }

    if (_.isString(templateAuth) && templateAuth === 'open') {
        return true;
    }

    if (templateAuth.method === 'open') {
        return true;
    }

    if (templateAuth.method === 'token') {
        return _.intersection(templateAuth.valid_tokens, authTokens).length > 0;
    }

    return false;
};

// Perform placeholder substitutions on a template
//
// @param template a template object (will not be modified)
//
// @param params an object containing named subsitution parameters
//        Only the ones found in the template's placeholders object
//        will be used, with missing ones taking default values.
//
// @returns a layergroup configuration
//
// @throws Error on malformed template or parameter
//
var _reNumber = /^([-+]?[\d\.]?\d+([eE][+-]?\d+)?)$/,
    _reCSSColorName = /^[a-zA-Z]+$/,
    _reCSSColorVal = /^#[0-9a-fA-F]{3,6}$/;

function _replaceVars (str, params) {
  //return _.template(str, params); // lazy way, possibly dangerous
  // Construct regular expressions for each param
    Object.keys(params).forEach(function(k) {
        str = str.replace(new RegExp("<%=\\s*" + k + "\\s*%>", "g"), params[k]);
    });
    return str;
}
o.instance = function(template, params) {
  var all_params = {};
  var phold = template.placeholders || {};
  Object.keys(phold).forEach(function(k) {
    var val = params.hasOwnProperty(k) ? params[k] : phold[k].default;
    var type = phold[k].type;
    // properly escape
    if ( type === 'sql_literal' ) {
      // duplicate any single-quote
      val = val.replace(/'/g, "''");
    }
    else if ( type === 'sql_ident' ) {
      // duplicate any double-quote
      val = val.replace(/"/g, '""');
    }
    else if ( type === 'number' ) {
      // check it's a number
      if ( typeof(val) !== 'number' && ! val.match(_reNumber) ) {
        throw new Error("Invalid number value for template parameter '" + k + "': " + val);
      }
    }
    else if ( type === 'css_color' ) {
      // check it only contains letters or
      // starts with # and only contains hexdigits
      if ( ! val.match(_reCSSColorName) && ! val.match(_reCSSColorVal) ) {
        throw new Error("Invalid css_color value for template parameter '" + k + "': " + val);
      }
    }
    else {
      // NOTE: should be checked at template create/update time
      throw new Error("Invalid placeholder type '" + type + "'");
    }
    all_params[k] = val;
  });

    // // NOTE: we're deep-cloning the layergroup here
    var layergroup = JSON.parse(JSON.stringify(template.layergroup));
    var visibility = template.layer_visibility || {};

    layergroup.layers = layergroup.layers
        .map(function (layer, i) {
            if (visibility[layer.id] === false || visibility[''+i] === false) {
                return;
            }

            if (params.styles && params.styles[i]) {
                // dynamic styling for this layer
                layer.options.cartocss = params.styles[i];
            } else if (layer.options.cartocss) {
                layer.options.cartocss = _replaceVars(layer.options.cartocss, all_params);
            }

            if (layer.options.sql) {
                layer.options.sql = _replaceVars(layer.options.sql, all_params);
            }

            return layer;
        })
        .filter(function (layer) {
            return !!layer;
        });

  // extra information about the template
  layergroup.template = {
    name: template.name,
    auth: template.auth
  };

  return layergroup;
};

// Return a fingerPrint of the object
o.fingerPrint = function(template) {
  return crypto.createHash('md5')
    .update(JSON.stringify(template))
    .digest('hex')
  ;
};

module.exports.templateName = function templateName(templateId) {
    var templateIdTokens = templateId.split('@');
    var name = templateIdTokens[0];

    if (templateIdTokens.length > 1) {
        name = templateIdTokens[1];
    }

    return name;
};
<commit_msg>Undo unneeded optimization<commit_after>var assert = require('assert');
var crypto = require('crypto');
var debug = require('debug')('windshaft:templates');
var step = require('step');
var _ = require('underscore');
var dot = require('dot');

var EventEmitter = require('events').EventEmitter;
var util = require('util');

// Class handling map templates
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps
//
// @param redis_pool an instance of a "redis-mpool"
//        See https://github.com/CartoDB/node-redis-mpool
//        Needs version 0.x.x of the API.
//
// @param opts TemplateMap options. Supported elements:
//    'max_user_templates' limit on the number of per-user
//
//
function TemplateMaps(redis_pool, opts) {
  if (!(this instanceof TemplateMaps)) {
      return new TemplateMaps();
  }

  EventEmitter.call(this);

  this.redis_pool = redis_pool;
  this.opts = opts || {};

  // Database containing templates
  // TODO: allow configuring ?
  // NOTE: currently it is the same as
  //       the one containing layergroups
  this.db_signatures = 0;

  //
  // Map templates are owned by a user that specifies access permissions
  // for their instances.
  //
  // We have the following datastores:
  //
  //  1. User templates: set of per-user map templates

  // User templates (HASH:tpl_id->tpl_val)
  this.key_usr_tpl = dot.template("map_tpl|{{=it.owner}}");
}

util.inherits(TemplateMaps, EventEmitter);

module.exports = TemplateMaps;

var o = TemplateMaps.prototype;

//--------------- PRIVATE METHODS --------------------------------

o._userTemplateLimit = function() {
  return this.opts.max_user_templates || 0;
};

/**
 * Internal function to communicate with redis
 *
 * @param redisFunc - the redis function to execute
 * @param redisArgs - the arguments for the redis function in an array
 * @param callback - function to pass results too.
 */
o._redisCmd = function(redisFunc, redisArgs, callback) {
  var redisClient;
  var that = this;
  var db = that.db_signatures;

  step(
    function getRedisClient() {
      that.redis_pool.acquire(db, this);
    },
    function executeQuery(err, data) {
      assert.ifError(err);
      redisClient = data;
      redisArgs.push(this);
      redisClient[redisFunc.toUpperCase()].apply(redisClient, redisArgs);
    },
    function releaseRedisClient(err, data) {
      if ( ! _.isUndefined(redisClient) ) {
          that.redis_pool.release(db, redisClient);
      }
      callback(err, data);
    }
  );
};

var _reValidNameIdentifier = /^[a-z0-9][0-9a-z_\-]*$/i;
var _reValidPlaceholderIdentifier = /^[a-z][0-9a-z_]*$/i;
// jshint maxcomplexity:15
o._checkInvalidTemplate = function(template) {
  if ( template.version !== '0.0.1' ) {
    return new Error("Unsupported template version " + template.version);
  }
  var tplname = template.name;
  if ( ! tplname ) {
    return new Error("Missing template name");
  }
  if ( ! tplname.match(_reValidNameIdentifier) ) {
    return new Error("Invalid characters in template name '" + tplname + "'");
  }

  var invalidError = isInvalidLayergroup(template.layergroup);
  if (invalidError) {
    return invalidError;
  }

  var placeholders = template.placeholders || {};

  var placeholderKeys = Object.keys(placeholders);
  for (var i = 0, len = placeholderKeys.length; i < len; i++) {
      var placeholderKey = placeholderKeys[i];

      if (!placeholderKey.match(_reValidPlaceholderIdentifier)) {
          return new Error("Invalid characters in placeholder name '" + placeholderKey + "'");
      }
      if ( ! placeholders[placeholderKey].hasOwnProperty('default') ) {
          return new Error("Missing default for placeholder '" + placeholderKey + "'");
      }
      if ( ! placeholders[placeholderKey].hasOwnProperty('type') ) {
          return new Error("Missing type for placeholder '" + placeholderKey + "'");
      }
  }

    var auth = template.auth || {};

    switch ( auth.method ) {
        case 'open':
            break;
        case 'token':
            if ( ! _.isArray(auth.valid_tokens) ) {
                return new Error("Invalid 'token' authentication: missing valid_tokens");
            }
            if ( ! auth.valid_tokens.length ) {
                return new Error("Invalid 'token' authentication: no valid_tokens");
            }
            break;
        default:
            return new Error("Unsupported authentication method: " + auth.method);
    }

    return false;
};

function isInvalidLayergroup(layergroup) {
    if (!layergroup) {
        return new Error('Missing layergroup');
    }

    var layers = layergroup.layers;

    if (!_.isArray(layers) || layers.length === 0) {
        return new Error('Missing or empty layers array from layergroup config');
    }

    var invalidLayers = layers
        .map(function(layer, layerIndex) {
            return layer.options ? null : layerIndex;
        })
        .filter(function(layerIndex) {
            return layerIndex !== null;
        });

    if (invalidLayers.length) {
        return new Error('Missing `options` in layergroup config for layers: ' + invalidLayers.join(', '));
    }

    return false;
}

function templateDefaults(template) {
    var templateAuth = _.defaults({}, template.auth || {}, {
        method: 'open'
    });
    return _.defaults({ auth: templateAuth }, template, {
        placeholders: {}
    });
}

//--------------- PUBLIC API -------------------------------------

// Add a template
//
// NOTE: locks user+template_name or fails
//
// @param owner cartodb username of the template owner
//
// @param template layergroup template, see
//        http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
//
// @param callback function(err, tpl_id)
//        Return template identifier (only valid for given user)
//
o.addTemplate = function(owner, template, callback) {
    var self = this;

    template = templateDefaults(template);

    var invalidError = this._checkInvalidTemplate(template);
    if ( invalidError ) {
        return callback(invalidError);
    }

    var templateName = template.name;
    var userTemplatesKey = this.key_usr_tpl({ owner:owner });
    var limit = this._userTemplateLimit();

    step(
        function checkLimit() {
            if ( ! limit ) {
                return 0;
            }
            self._redisCmd('HLEN', [ userTemplatesKey ], this);
        },
        function installTemplateIfDoesNotExist(err, numberOfTemplates) {
            assert.ifError(err);
            if ( limit && numberOfTemplates >= limit ) {
                var limitReachedError = new Error("User '" + owner + "' reached limit on number of templates (" +
                    numberOfTemplates + "/" + limit + ")");
                limitReachedError.http_status = 409;
                throw limitReachedError;
            }
            self._redisCmd('HSETNX', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
        },
        function validateInstallation(err, wasSet) {
            assert.ifError(err);
            if ( ! wasSet ) {
                throw new Error("Template '" + templateName + "' of user '" + owner + "' already exists");
            }

            return true;
        },
        function finish(err) {
            if (!err) {
                self.emit('add', owner, templateName, template);
            }

            callback(err, templateName, template);
        }
  );
};

// Delete a template
//
// @param owner cartodb username of the template owner
//
// @param tpl_id template identifier as returned
//        by addTemplate or listTemplates
//
// @param callback function(err)
//
o.delTemplate = function(owner, tpl_id, callback) {
    var self = this;
    step(
        function deleteTemplate() {
            self._redisCmd('HDEL', [ self.key_usr_tpl({ owner:owner }), tpl_id ], this);
        },
        function handleDeletion(err, deleted) {
            assert.ifError(err);
            if (!deleted) {
                throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
            }
            return true;
        },
        function finish(err) {
            if (!err) {
                self.emit('delete', owner, tpl_id);
            }

            callback(err);
        }
    );
};

// Update a template
//
// NOTE: locks user+template_name or fails
//
// Also deletes and re-creates associated authentication certificate,
// which in turn deletes all instance signatures
//
// @param owner cartodb username of the template owner
//
// @param tpl_id template identifier as returned by addTemplate
//
// @param template layergroup template, see
//        http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
//
// @param callback function(err)
//
o.updTemplate = function(owner, tpl_id, template, callback) {
    var self = this;

    template = templateDefaults(template);

    var invalidError = this._checkInvalidTemplate(template);

    if ( invalidError ) {
        return callback(invalidError);
    }

    var templateName = template.name;

    if ( tpl_id !== templateName ) {
        return callback(new Error("Cannot update name of a map template ('" + tpl_id + "' != '" + templateName + "')"));
    }

    var userTemplatesKey = this.key_usr_tpl({ owner:owner });

    var previousTemplate = null;

    step(
        function getExistingTemplate() {
            self._redisCmd('HGET', [ userTemplatesKey, tpl_id ], this);
        },
        function updateTemplate(err, _currentTemplate) {
            assert.ifError(err);
            if (!_currentTemplate) {
                throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
            }
            previousTemplate = _currentTemplate;
            self._redisCmd('HSET', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
        },
        function handleTemplateUpdate(err, didSetNewField) {
            assert.ifError(err);
            if (didSetNewField) {
                debug('New template created on update operation');
            }
            return true;
        },
        function finish(err) {
            if (!err) {
                if (self.fingerPrint(JSON.parse(previousTemplate)) !== self.fingerPrint(template)) {
                    self.emit('update', owner, templateName, template);
                }
            }

            callback(err, template);
        }
    );
};

// List user templates
//
// @param owner cartodb username of the templates owner
//
// @param callback function(err, tpl_id_list)
//        Returns a list of template identifiers
//
o.listTemplates = function(owner, callback) {
    this._redisCmd('HKEYS', [ this.key_usr_tpl({owner:owner}) ], callback);
};

// Get a templates
//
// @param owner cartodb username of the template owner
//
// @param tpl_id template identifier as returned
//        by addTemplate or listTemplates
//
// @param callback function(err, template)
//        Return full template definition
//
o.getTemplate = function(owner, tpl_id, callback) {
    var self = this;
    step(
        function getTemplate() {
            self._redisCmd('HGET', [ self.key_usr_tpl({owner:owner}), tpl_id ], this);
        },
        function parseTemplate(err, tpl_val) {
            assert.ifError(err);
            return JSON.parse(tpl_val);
        },
        function finish(err, tpl) {
            callback(err, tpl);
        }
    );
};

o.isAuthorized = function(template, authTokens) {
    if (!template) {
        return false;
    }

    authTokens = _.isArray(authTokens) ? authTokens : [authTokens];

    var templateAuth = template.auth;

    if (!templateAuth) {
        return false;
    }

    if (_.isString(templateAuth) && templateAuth === 'open') {
        return true;
    }

    if (templateAuth.method === 'open') {
        return true;
    }

    if (templateAuth.method === 'token') {
        return _.intersection(templateAuth.valid_tokens, authTokens).length > 0;
    }

    return false;
};

// Perform placeholder substitutions on a template
//
// @param template a template object (will not be modified)
//
// @param params an object containing named subsitution parameters
//        Only the ones found in the template's placeholders object
//        will be used, with missing ones taking default values.
//
// @returns a layergroup configuration
//
// @throws Error on malformed template or parameter
//
var _reNumber = /^([-+]?[\d\.]?\d+([eE][+-]?\d+)?)$/,
    _reCSSColorName = /^[a-zA-Z]+$/,
    _reCSSColorVal = /^#[0-9a-fA-F]{3,6}$/;

function _replaceVars (str, params) {
  //return _.template(str, params); // lazy way, possibly dangerous
  // Construct regular expressions for each param
    Object.keys(params).forEach(function(k) {
        str = str.replace(new RegExp("<%=\\s*" + k + "\\s*%>", "g"), params[k]);
    });
    return str;
}
o.instance = function(template, params) {
  var all_params = {};
  var phold = template.placeholders || {};
  Object.keys(phold).forEach(function(k) {
    var val = params.hasOwnProperty(k) ? params[k] : phold[k].default;
    var type = phold[k].type;
    // properly escape
    if ( type === 'sql_literal' ) {
      // duplicate any single-quote
      val = val.replace(/'/g, "''");
    }
    else if ( type === 'sql_ident' ) {
      // duplicate any double-quote
      val = val.replace(/"/g, '""');
    }
    else if ( type === 'number' ) {
      // check it's a number
      if ( typeof(val) !== 'number' && ! val.match(_reNumber) ) {
        throw new Error("Invalid number value for template parameter '" + k + "': " + val);
      }
    }
    else if ( type === 'css_color' ) {
      // check it only contains letters or
      // starts with # and only contains hexdigits
      if ( ! val.match(_reCSSColorName) && ! val.match(_reCSSColorVal) ) {
        throw new Error("Invalid css_color value for template parameter '" + k + "': " + val);
      }
    }
    else {
      // NOTE: should be checked at template create/update time
      throw new Error("Invalid placeholder type '" + type + "'");
    }
    all_params[k] = val;
  });

  // NOTE: we're deep-cloning the layergroup here
  var layergroup = JSON.parse(JSON.stringify(template.layergroup));
  for (var i=0; i<layergroup.layers.length; ++i) {
    var lyropt = layergroup.layers[i].options;

    if ( params.styles && params.styles[i] ) {
        // dynamic styling for this layer
        lyropt.cartocss = params.styles[i];
    } else if ( lyropt.cartocss ) {
        lyropt.cartocss = _replaceVars(lyropt.cartocss, all_params);
    }
    if ( lyropt.sql) {
        lyropt.sql = _replaceVars(lyropt.sql, all_params);
    }
    // Anything else ?
  }

  // extra information about the template
  layergroup.template = {
    name: template.name,
    auth: template.auth
  };

  return layergroup;
};

// Return a fingerPrint of the object
o.fingerPrint = function(template) {
  return crypto.createHash('md5')
    .update(JSON.stringify(template))
    .digest('hex')
  ;
};

module.exports.templateName = function templateName(templateId) {
    var templateIdTokens = templateId.split('@');
    var name = templateIdTokens[0];

    if (templateIdTokens.length > 1) {
        name = templateIdTokens[1];
    }

    return name;
};
<|endoftext|>


<commit_before>const NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
const cors = require('../middleware/cors');
const userMiddleware = require('../middleware/user');
const allowQueryParams = require('../middleware/allow-query-params');
const vectorError = require('../middleware/vector-error');

const DEFAULT_ZOOM_CENTER = {
    zoom: 1,
    center: {
        lng: 0,
        lat: 0
    }
};

function numMapper(n) {
    return +n;
}

function getRequestParams(locals) {
    const params = Object.assign({}, locals);

    delete params.template;
    delete params.affectedTablesAndLastUpdate;
    delete params.namedMapProvider;
    delete params.allowedQueryParams;

    return params;
}

function NamedMapsController(prepareContext, namedMapProviderCache, tileBackend, previewBackend,
                             surrogateKeysCache, tablesExtentApi, metadataBackend) {
    this.namedMapProviderCache = namedMapProviderCache;
    this.tileBackend = tileBackend;
    this.previewBackend = previewBackend;
    this.surrogateKeysCache = surrogateKeysCache;
    this.tablesExtentApi = tablesExtentApi;
    this.metadataBackend = metadataBackend;
    this.prepareContext = prepareContext;
}

module.exports = NamedMapsController;

NamedMapsController.prototype.register = function(app) {
    const tileOptions = {
        namedMapProviderCache: this.namedMapProviderCache,
        tileBackend: this.tileBackend,
        label: 'NAMED_MAP_TILE'
    };

    app.get(
        app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)',
        cors(),
        userMiddleware(),
        this.prepareContext,
        getNamedMapProvider(tileOptions),
        getAffectedTables(),
        getTile(tileOptions),
        this.setSurrogateKey(),
        setCacheChannelHeader(),
        this.setLastModifiedHeader(),
        this.setCacheControlHeader(),
        this.setContentTypeHeader(),
        this.respond(),
        vectorError()
    );

    const staticOptions = {
        previewBackend: this.previewBackend,
        namedMapProviderCache: this.namedMapProviderCache,
        forcedFormat: 'png',
        label: 'STATIC_VIZ_MAP'
    };

    app.get(
        app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format',
        cors(),
        userMiddleware(),
        allowQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
        this.prepareContext,
        getNamedMapProvider(staticOptions),
        getAffectedTables(),
        getTemplate(staticOptions),
        prepareLayerFilterFromPreviewLayers(staticOptions),
        getStaticImageOptions({ tablesExtentApi: this.tablesExtentApi }),
        getImage(staticOptions),
        incrementMapViews({ metadataBackend: this.metadataBackend }),
        this.setSurrogateKey(),
        setCacheChannelHeader(),
        this.setLastModifiedHeader(),
        this.setCacheControlHeader(),
        this.setContentTypeHeader(),
        this.respond()
    );
};

function getNamedMapProvider ({ namedMapProviderCache, label, forcedFormat = null }) {
    return function getNamedMapProviderMiddleware (req, res, next) {
        const { user } = res.locals;
        const { config, auth_token } = req.query;
        const { template_id } = req.params;

        if (forcedFormat) {
            res.locals.format = forcedFormat;
            res.locals.layer = res.locals.layer || 'all';
        }

        const params = getRequestParams(res.locals);

        namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, namedMapProvider) => {
            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.namedMapProvider = namedMapProvider;

            next();
        });
    };
}

function getAffectedTables () {
    return function getAffectedTables (req, res, next) {
        const { namedMapProvider } = res.locals;

        namedMapProvider.getAffectedTablesAndLastUpdatedTime((err, affectedTablesAndLastUpdate) => {
            req.profiler.done('affectedTables');

            if (err) {
                return next(err);
            }

            res.locals.affectedTablesAndLastUpdate = affectedTablesAndLastUpdate;

            next();
        });
    };
}

function getTemplate ({ label }) {
    return function getTemplateMiddleware (req, res, next) {
        const { namedMapProvider } = res.locals;

        namedMapProvider.getTemplate((err, template) => {
            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.template = template;

            next();
        });
    };
}

function prepareLayerFilterFromPreviewLayers ({ namedMapProviderCache, label }) {
    return function prepareLayerFilterFromPreviewLayersMiddleware (req, res, next) {
        const { user, template } = res.locals;
        const { template_id } = req.params;
        const { config, auth_token } = req.query;

        if (!template || !template.view || !template.view.preview_layers) {
            return next();
        }

        var previewLayers = template.view.preview_layers;
        var layerVisibilityFilter = [];

        template.layergroup.layers.forEach((layer, index) => {
            if (previewLayers[''+index] !== false && previewLayers[layer.id] !== false) {
                layerVisibilityFilter.push(''+index);
            }
        });

        if (!layerVisibilityFilter.length) {
            return next();
        }

        const params = getRequestParams(res.locals);

        // overwrites 'all' default filter
        params.layer = layerVisibilityFilter.join(',');

        // recreates the provider
        namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, provider) => {
            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.namedMapProvider = provider;

            next();
        });
    };
}

function getTile ({ tileBackend, label }) {
    return function getTileMiddleware (req, res, next) {
        const { namedMapProvider } = res.locals;

        tileBackend.getTile(namedMapProvider, req.params, (err, tile, headers, stats) => {
            req.profiler.add(stats);

            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.body = tile;
            res.locals.headers = headers;
            res.locals.stats = stats;

            next();
        });
    };
}

function getStaticImageOptions ({ tablesExtentApi }) {
    return function getStaticImageOptionsMiddleware(req, res, next) {
        const { user, namedMapProvider, template } = res.locals;

        const imageOpts = getImageOptions(res.locals, template);

        if (imageOpts) {
            res.locals.imageOpts = imageOpts;
            return next();
        }

        res.locals.imageOpts = DEFAULT_ZOOM_CENTER;

        namedMapProvider.getAffectedTablesAndLastUpdatedTime((err, affectedTablesAndLastUpdate) => {
            if (err) {
                return next();
            }

            var affectedTables = affectedTablesAndLastUpdate.tables || [];

            if (affectedTables.length === 0) {
                return next();
            }

            tablesExtentApi.getBounds(user, affectedTables, (err, bounds) => {
                if (err) {
                    return next();
                }

                res.locals.imageOpts = bounds;

                return next();
            });
        });
    };
}

function getImageOptions (params, template) {
    const { zoom, lon, lat, bbox } = params;

    let imageOpts = getImageOptionsFromCoordinates(zoom, lon, lat);
    if (imageOpts) {
        return imageOpts;
    }

    imageOpts = getImageOptionsFromBoundingBox(bbox);
    if (imageOpts) {
        return imageOpts;
    }

    imageOpts = getImageOptionsFromTemplate(template, zoom);
    if (imageOpts) {
        return imageOpts;
    }
}

function getImageOptionsFromCoordinates (zoom, lon, lat) {
    if ([zoom, lon, lat].map(numMapper).every(Number.isFinite)) {
        return {
            zoom: zoom,
            center: {
                lng: lon,
                lat: lat
            }
        };
    }
}

function getImageOptionsFromTemplate (template, zoom) {
    if (template.view) {
        var zoomCenter = templateZoomCenter(template.view);
        if (zoomCenter) {
            if (Number.isFinite(+zoom)) {
                zoomCenter.zoom = +zoom;
            }

            return zoomCenter;
        }

        var bounds = templateBounds(template.view);
        if (bounds) {
            return bounds;
        }
    }
}

function getImageOptionsFromBoundingBox (bbox = '') {
    var _bbox = bbox.split(',').map(numMapper);

    if (_bbox.length === 4 && _bbox.every(Number.isFinite)) {
        return {
            bounds: {
                west: _bbox[0],
                south: _bbox[1],
                east: _bbox[2],
                north: _bbox[3]
            }
        };
    }
}

function getImage({ previewBackend, label }) {
    return function getImageMiddleware (req, res, next) {
        const { imageOpts, namedMapProvider } = res.locals;
        const { zoom, center, bounds } = imageOpts;

        let { width, height } = req.params;

        width = +width;
        height = +height;

        const format = req.params.format === 'jpg' ? 'jpeg' : 'png';

        if (zoom !== undefined && center) {
            return previewBackend.getImage(namedMapProvider, format, width, height, zoom, center,
                (err, image, headers, stats) => {
                if (err) {
                    err.label = label;
                    return next(err);
                }

                res.locals.body = image;
                res.locals.headers = headers;
                res.locals.stats = stats;

                next();
            });
        }

        previewBackend.getImage(namedMapProvider, format, width, height, bounds, (err, image, headers, stats) => {
            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.body = image;
            res.locals.headers = headers;
            res.locals.stats = stats;

            next();
        });
    };
}

function incrementMapViewsError (ctx) {
    return `ERROR: failed to increment mapview count for user '${ctx.user}': ${ctx.err}`;
}

function incrementMapViews ({ metadataBackend }) {
    return function incrementMapViewsMiddleware(req, res, next) {
        const { user, namedMapProvider } = res.locals;

        namedMapProvider.getMapConfig((err, mapConfig) => {
            if (err) {
                global.logger.log(incrementMapViewsError({ user, err }));
                return next();
            }

            const statTag = mapConfig.obj().stat_tag;

            metadataBackend.incMapviewCount(user, statTag, (err) => {
                if (err) {
                    global.logger.log(incrementMapViewsError({ user, err }));
                }

                next();
            });
        });
    };
}

function templateZoomCenter(view) {
    if (view.zoom !== undefined && view.center) {
        return {
            zoom: view.zoom,
            center: view.center
        };
    }
    return false;
}

function templateBounds(view) {
    if (view.bounds) {
        var hasAllBounds = ['west', 'south', 'east', 'north'].every(prop => Number.isFinite(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;
}

function setCacheChannelHeader () {
    return function setCacheChannelHeaderMiddleware (req, res, next) {
        const { affectedTablesAndLastUpdate } = res.locals;

        if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
            res.set('X-Cache-Channel', affectedTablesAndLastUpdate.getCacheChannel());
        }

        next();
    };
}

NamedMapsController.prototype.setSurrogateKey = function () {
    return function setSurrogateKeyMiddleware(req, res, next) {
        const { user, namedMapProvider, affectedTablesAndLastUpdate } = res.locals;

        this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, namedMapProvider.getTemplateName()));
        if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
            if (affectedTablesAndLastUpdate.tables.length > 0) {
                this.surrogateKeysCache.tag(res, affectedTablesAndLastUpdate);
            }
        }

        next();
    }.bind(this);
};

NamedMapsController.prototype.setLastModifiedHeader = function () {
    return function setLastModifiedHeaderMiddleware(req, res, next) {
        const { affectedTablesAndLastUpdate } = res.locals;

        if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
            var lastModifiedDate;
            if (Number.isFinite(affectedTablesAndLastUpdate.lastUpdatedTime)) {
                lastModifiedDate = new Date(affectedTablesAndLastUpdate.getLastUpdatedAt());
            } else {
                lastModifiedDate = new Date();
            }

            res.set('Last-Modified', lastModifiedDate.toUTCString());
        }

        next();
    };
 };

NamedMapsController.prototype.setCacheControlHeader = function () {
    return function setCacheControlHeaderMiddleware(req, res, next) {
        const { affectedTablesAndLastUpdate } = res.locals;

        res.set('Cache-Control', 'public,max-age=7200,must-revalidate');

        if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
            // we increase cache control as we can invalidate it
            res.set('Cache-Control', 'public,max-age=31536000');
        }

        next();
    };
 };

NamedMapsController.prototype.setContentTypeHeader = function () {
    return function setContentTypeHeaderMiddleware(req, res, next) {
        const { headers = {} } = res.locals;

        res.set('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png');

        next();
    };
};

NamedMapsController.prototype.respond = function () {
    return function respondMiddleware (req, res) {
        const { body, stats = {}, format } = res.locals;

        req.profiler.done('render-' + format);
        req.profiler.add(stats);

        res.status(200);
        res.send(body);
    };
};
<commit_msg>Extract setCacheChannelHeader and setSurrogateKeyHeader from controller's context<commit_after>const NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
const cors = require('../middleware/cors');
const userMiddleware = require('../middleware/user');
const allowQueryParams = require('../middleware/allow-query-params');
const vectorError = require('../middleware/vector-error');

const DEFAULT_ZOOM_CENTER = {
    zoom: 1,
    center: {
        lng: 0,
        lat: 0
    }
};

function numMapper(n) {
    return +n;
}

function getRequestParams(locals) {
    const params = Object.assign({}, locals);

    delete params.template;
    delete params.affectedTablesAndLastUpdate;
    delete params.namedMapProvider;
    delete params.allowedQueryParams;

    return params;
}

function NamedMapsController(prepareContext, namedMapProviderCache, tileBackend, previewBackend,
                             surrogateKeysCache, tablesExtentApi, metadataBackend) {
    this.namedMapProviderCache = namedMapProviderCache;
    this.tileBackend = tileBackend;
    this.previewBackend = previewBackend;
    this.surrogateKeysCache = surrogateKeysCache;
    this.tablesExtentApi = tablesExtentApi;
    this.metadataBackend = metadataBackend;
    this.prepareContext = prepareContext;
}

module.exports = NamedMapsController;

NamedMapsController.prototype.register = function(app) {
    const tileOptions = {
        namedMapProviderCache: this.namedMapProviderCache,
        tileBackend: this.tileBackend,
        label: 'NAMED_MAP_TILE'
    };

    app.get(
        app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)',
        cors(),
        userMiddleware(),
        this.prepareContext,
        getNamedMapProvider(tileOptions),
        getAffectedTables(),
        getTile(tileOptions),
        setSurrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
        setCacheChannelHeader(),
        this.setLastModifiedHeader(),
        this.setCacheControlHeader(),
        this.setContentTypeHeader(),
        this.respond(),
        vectorError()
    );

    const staticOptions = {
        previewBackend: this.previewBackend,
        namedMapProviderCache: this.namedMapProviderCache,
        forcedFormat: 'png',
        label: 'STATIC_VIZ_MAP'
    };

    app.get(
        app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format',
        cors(),
        userMiddleware(),
        allowQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
        this.prepareContext,
        getNamedMapProvider(staticOptions),
        getAffectedTables(),
        getTemplate(staticOptions),
        prepareLayerFilterFromPreviewLayers(staticOptions),
        getStaticImageOptions({ tablesExtentApi: this.tablesExtentApi }),
        getImage(staticOptions),
        incrementMapViews({ metadataBackend: this.metadataBackend }),
        setSurrogateKeyHeader({ surrogateKeysCache: this.surrogateKeysCache }),
        setCacheChannelHeader(),
        this.setLastModifiedHeader(),
        this.setCacheControlHeader(),
        this.setContentTypeHeader(),
        this.respond()
    );
};

function getNamedMapProvider ({ namedMapProviderCache, label, forcedFormat = null }) {
    return function getNamedMapProviderMiddleware (req, res, next) {
        const { user } = res.locals;
        const { config, auth_token } = req.query;
        const { template_id } = req.params;

        if (forcedFormat) {
            res.locals.format = forcedFormat;
            res.locals.layer = res.locals.layer || 'all';
        }

        const params = getRequestParams(res.locals);

        namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, namedMapProvider) => {
            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.namedMapProvider = namedMapProvider;

            next();
        });
    };
}

function getAffectedTables () {
    return function getAffectedTables (req, res, next) {
        const { namedMapProvider } = res.locals;

        namedMapProvider.getAffectedTablesAndLastUpdatedTime((err, affectedTablesAndLastUpdate) => {
            req.profiler.done('affectedTables');

            if (err) {
                return next(err);
            }

            res.locals.affectedTablesAndLastUpdate = affectedTablesAndLastUpdate;

            next();
        });
    };
}

function getTemplate ({ label }) {
    return function getTemplateMiddleware (req, res, next) {
        const { namedMapProvider } = res.locals;

        namedMapProvider.getTemplate((err, template) => {
            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.template = template;

            next();
        });
    };
}

function prepareLayerFilterFromPreviewLayers ({ namedMapProviderCache, label }) {
    return function prepareLayerFilterFromPreviewLayersMiddleware (req, res, next) {
        const { user, template } = res.locals;
        const { template_id } = req.params;
        const { config, auth_token } = req.query;

        if (!template || !template.view || !template.view.preview_layers) {
            return next();
        }

        var previewLayers = template.view.preview_layers;
        var layerVisibilityFilter = [];

        template.layergroup.layers.forEach((layer, index) => {
            if (previewLayers[''+index] !== false && previewLayers[layer.id] !== false) {
                layerVisibilityFilter.push(''+index);
            }
        });

        if (!layerVisibilityFilter.length) {
            return next();
        }

        const params = getRequestParams(res.locals);

        // overwrites 'all' default filter
        params.layer = layerVisibilityFilter.join(',');

        // recreates the provider
        namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, provider) => {
            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.namedMapProvider = provider;

            next();
        });
    };
}

function getTile ({ tileBackend, label }) {
    return function getTileMiddleware (req, res, next) {
        const { namedMapProvider } = res.locals;

        tileBackend.getTile(namedMapProvider, req.params, (err, tile, headers, stats) => {
            req.profiler.add(stats);

            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.body = tile;
            res.locals.headers = headers;
            res.locals.stats = stats;

            next();
        });
    };
}

function getStaticImageOptions ({ tablesExtentApi }) {
    return function getStaticImageOptionsMiddleware(req, res, next) {
        const { user, namedMapProvider, template } = res.locals;

        const imageOpts = getImageOptions(res.locals, template);

        if (imageOpts) {
            res.locals.imageOpts = imageOpts;
            return next();
        }

        res.locals.imageOpts = DEFAULT_ZOOM_CENTER;

        namedMapProvider.getAffectedTablesAndLastUpdatedTime((err, affectedTablesAndLastUpdate) => {
            if (err) {
                return next();
            }

            var affectedTables = affectedTablesAndLastUpdate.tables || [];

            if (affectedTables.length === 0) {
                return next();
            }

            tablesExtentApi.getBounds(user, affectedTables, (err, bounds) => {
                if (err) {
                    return next();
                }

                res.locals.imageOpts = bounds;

                return next();
            });
        });
    };
}

function getImageOptions (params, template) {
    const { zoom, lon, lat, bbox } = params;

    let imageOpts = getImageOptionsFromCoordinates(zoom, lon, lat);
    if (imageOpts) {
        return imageOpts;
    }

    imageOpts = getImageOptionsFromBoundingBox(bbox);
    if (imageOpts) {
        return imageOpts;
    }

    imageOpts = getImageOptionsFromTemplate(template, zoom);
    if (imageOpts) {
        return imageOpts;
    }
}

function getImageOptionsFromCoordinates (zoom, lon, lat) {
    if ([zoom, lon, lat].map(numMapper).every(Number.isFinite)) {
        return {
            zoom: zoom,
            center: {
                lng: lon,
                lat: lat
            }
        };
    }
}

function getImageOptionsFromTemplate (template, zoom) {
    if (template.view) {
        var zoomCenter = templateZoomCenter(template.view);
        if (zoomCenter) {
            if (Number.isFinite(+zoom)) {
                zoomCenter.zoom = +zoom;
            }

            return zoomCenter;
        }

        var bounds = templateBounds(template.view);
        if (bounds) {
            return bounds;
        }
    }
}

function getImageOptionsFromBoundingBox (bbox = '') {
    var _bbox = bbox.split(',').map(numMapper);

    if (_bbox.length === 4 && _bbox.every(Number.isFinite)) {
        return {
            bounds: {
                west: _bbox[0],
                south: _bbox[1],
                east: _bbox[2],
                north: _bbox[3]
            }
        };
    }
}

function getImage({ previewBackend, label }) {
    return function getImageMiddleware (req, res, next) {
        const { imageOpts, namedMapProvider } = res.locals;
        const { zoom, center, bounds } = imageOpts;

        let { width, height } = req.params;

        width = +width;
        height = +height;

        const format = req.params.format === 'jpg' ? 'jpeg' : 'png';

        if (zoom !== undefined && center) {
            return previewBackend.getImage(namedMapProvider, format, width, height, zoom, center,
                (err, image, headers, stats) => {
                if (err) {
                    err.label = label;
                    return next(err);
                }

                res.locals.body = image;
                res.locals.headers = headers;
                res.locals.stats = stats;

                next();
            });
        }

        previewBackend.getImage(namedMapProvider, format, width, height, bounds, (err, image, headers, stats) => {
            if (err) {
                err.label = label;
                return next(err);
            }

            res.locals.body = image;
            res.locals.headers = headers;
            res.locals.stats = stats;

            next();
        });
    };
}

function incrementMapViewsError (ctx) {
    return `ERROR: failed to increment mapview count for user '${ctx.user}': ${ctx.err}`;
}

function incrementMapViews ({ metadataBackend }) {
    return function incrementMapViewsMiddleware(req, res, next) {
        const { user, namedMapProvider } = res.locals;

        namedMapProvider.getMapConfig((err, mapConfig) => {
            if (err) {
                global.logger.log(incrementMapViewsError({ user, err }));
                return next();
            }

            const statTag = mapConfig.obj().stat_tag;

            metadataBackend.incMapviewCount(user, statTag, (err) => {
                if (err) {
                    global.logger.log(incrementMapViewsError({ user, err }));
                }

                next();
            });
        });
    };
}

function templateZoomCenter(view) {
    if (view.zoom !== undefined && view.center) {
        return {
            zoom: view.zoom,
            center: view.center
        };
    }
    return false;
}

function templateBounds(view) {
    if (view.bounds) {
        var hasAllBounds = ['west', 'south', 'east', 'north'].every(prop => Number.isFinite(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;
}

function setSurrogateKeyHeader ({ surrogateKeysCache }) {
    return function setSurrogateKeyHeaderMiddleware(req, res, next) {
        const { user, namedMapProvider, affectedTablesAndLastUpdate } = res.locals;

        surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, namedMapProvider.getTemplateName()));
        if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
            if (affectedTablesAndLastUpdate.tables.length > 0) {
                surrogateKeysCache.tag(res, affectedTablesAndLastUpdate);
            }
        }

        next();
    };
}

function setCacheChannelHeader () {
    return function setCacheChannelHeaderMiddleware (req, res, next) {
        const { affectedTablesAndLastUpdate } = res.locals;

        if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
            res.set('X-Cache-Channel', affectedTablesAndLastUpdate.getCacheChannel());
        }

        next();
    };
}

NamedMapsController.prototype.setLastModifiedHeader = function () {
    return function setLastModifiedHeaderMiddleware(req, res, next) {
        const { affectedTablesAndLastUpdate } = res.locals;

        if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
            var lastModifiedDate;
            if (Number.isFinite(affectedTablesAndLastUpdate.lastUpdatedTime)) {
                lastModifiedDate = new Date(affectedTablesAndLastUpdate.getLastUpdatedAt());
            } else {
                lastModifiedDate = new Date();
            }

            res.set('Last-Modified', lastModifiedDate.toUTCString());
        }

        next();
    };
 };

NamedMapsController.prototype.setCacheControlHeader = function () {
    return function setCacheControlHeaderMiddleware(req, res, next) {
        const { affectedTablesAndLastUpdate } = res.locals;

        res.set('Cache-Control', 'public,max-age=7200,must-revalidate');

        if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
            // we increase cache control as we can invalidate it
            res.set('Cache-Control', 'public,max-age=31536000');
        }

        next();
    };
 };

NamedMapsController.prototype.setContentTypeHeader = function () {
    return function setContentTypeHeaderMiddleware(req, res, next) {
        const { headers = {} } = res.locals;

        res.set('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png');

        next();
    };
};

NamedMapsController.prototype.respond = function () {
    return function respondMiddleware (req, res) {
        const { body, stats = {}, format } = res.locals;

        req.profiler.done('render-' + format);
        req.profiler.add(stats);

        res.status(200);
        res.send(body);
    };
};
<|endoftext|>


<commit_before>Template.playingCard.helpers({
  suitString: function(suit) {
    var suits = {'H': '♥', 'S':'♠', 'D':'♦', 'C':'♣'}
    if (suits[suit] === "undefined"){
      throw Meteor.Error("suit-undefined", "It seems the suit is not H, S, D or C");
    }
    return Spacebars.SafeString(suits[suit]);
  },
  rankString: function(rank){
    if (rank == "A"){
      return "";
    }
    return rank;
  },
  isAce: function(rank){
    if (rank == "A"){
      return "ace";
    }
    return "";
  }
});
<commit_msg>Adde error handling<commit_after>Template.playingCard.helpers({
  suitString: function(suit) {
    var suits = {'H': '♥', 'S':'♠', 'D':'♦', 'C':'♣'}
    console.log(suits[suit]);
    if (typeof suits[suit] === "undefined"){
      throw Meteor.Error("not-a-suit", "It seems the suit is not H, S, D or C.");
    }
    return Spacebars.SafeString(suits[suit]);
  },
  rankString: function(rank){
    if (rank == "A"){
      return "";
    }
    return rank;
  },
  isAce: function(rank){
    if (rank == "A"){
      return "ace";
    }
    return "";
  }
});
<|endoftext|>


<commit_before>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

var __ = require('underscore');
var fs = require('fs');
var path = require('path');
var azure = require('azure');
var request = require('request');
var through = require('through');
var util = require('util');

var knownLocations = require('../location/knownLocations');
var validation = require('../../../util/validation');
var profile = require('../../../util/profile');
var utils = require('../../../util/utils');

var $ = utils.getLocaleString;

/**
* Validate that a given location is valid for group creation
* and prompts for location if not valid or not given.
*
* @param {string} location location requested
* @param {object} log object to print messages to user
* @param {object} interaction the interactor object to prompt with
* @param {function} callback callback received either error or final location
*/
exports.validateLocation = function validateLocation(location, log, interaction, callback) {
  var validLocations = knownLocations.getValidLocationsOfResourceGroup();

  if (location && validLocations.some(function (l) { return utils.ignoreCaseEquals(l, location); })) {
    return callback(null, location);
  }

  if (location) {
    log.info(util.format($('The location %s is not valid'), location));
  }

  log.info($('Choose location: '));
  interaction.choose(validLocations, function (err, locationIndex) {
    callback(err, validLocations[locationIndex]);
  });
};

exports.getGroup = function getGroup(client, name, callback) {
  client.resourceGroups.get(name, function (err, group) {
    if (err) {
      // 404 means doesn't exist
      if (err.statusCode === 404) {
        callback(null, null);
      } else {
        // some other error, let it out
        callback(err);
      }
    } else {
      // No error, group exists
      callback(null, group.resourceGroup);
    }
  });
};

exports.getResource = function getResource(client, resourceGroup, identity, callback) {
  client.resources.get(resourceGroup, identity, function (err, resource) {
    if (err) {
      // 404 means doesn't exist
      if (err.statusCode === 404) {
        callback(null, null);
      } else {
        // some other error, let it out
        callback(err);
      }
    } else {
      // No error, resource exists
      callback(null, resource.resource);
    }
  });
};

exports.createDeployment = function (cli, resourceGroup, name, options, _) {
  var subscription = profile.current.getSubscription(options.subscription);
  var client = subscription.createResourceClient('createResourceManagementClient');

  var templateParameters = createDeploymentParameters(cli, subscription, resourceGroup, options, _);

  //if not provided, derive it from the template file name
  if (!name) {
    var templateName = options.galleryTemplate || options.templateFile || options.templateUri;
    var baseTemplateName = path.basename(templateName);
    //if the file extension is '.json', get rid of it.
    if (utils.stringEndsWith(baseTemplateName, '.json', true)) {
      baseTemplateName = path.basename(baseTemplateName, path.extname(baseTemplateName));
    }
    name = baseTemplateName;
  }

  var result = cli.interaction.withProgress($('Creating a deployment'),
    function (log, _) {
      var validationResponse = client.deployments.validate(resourceGroup, name, templateParameters, _);
      var createResponse = client.deployments.createOrUpdate(resourceGroup, name, templateParameters, _);
      createResponse.requiredProviders = getTemplateProviders(validationResponse);
      return createResponse;
    }, _);

  cli.output.info(util.format($('Created template deployment "%s"'), name));

  // register providres required for new template
  addKnownProviders(subscription);

  cli.interaction.withProgress($('Registering providers'),
    function (log, _) {
      for (var i = 0; i < result.requiredProviders.length; i++) {
        var namespace = result.requiredProviders[i];
        if (!__.contains(profile.knownResourceNamespaces(), namespace)) {
          log.info(util.format($('Registering provider %s'), namespace));
          subscription.registerResourceNamespace(namespace, _);
        }
      }
    },
    _);

  return result.deployment;

};

exports.validateTemplate = function (cli, resourceGroup, options, _) {
  var subscription = profile.current.getSubscription(options.subscription);
  var client = subscription.createResourceClient('createResourceManagementClient');

  var templateParameters = createDeploymentParameters(cli, subscription, resourceGroup, options, _);

  var response = cli.interaction.withProgress($('Validating the template'),
    function (log, _) {
      return client.deployments.validate(resourceGroup, 'fakedDeploymentName', templateParameters, _);
    }, _);

  response.requiredProviders = getTemplateProviders(response);

  return response;
};

exports.getGalleryTemplateFile = function (subscription, galleryTemplateName, _) {
  var galleryClient = utils.createClient('createGalleryClient', new azure.AnonymousCloudCredentials(),
    subscription.galleryEndpointUrl);
  var galleryItem = galleryClient.items.get(galleryTemplateName, _).item;
  return exports.getTemplateDownloadUrl(galleryItem);
};

exports.getTemplateDownloadUrl = function getTemplateDownloadUrl(templateData) {
  var key = templateData.definitionTemplates.defaultDeploymentTemplateId;
  var urls = Object.keys(templateData.definitionTemplates.deploymentTemplateFileUrls)
    .filter(function (url) { return utils.ignoreCaseEquals(key, url); });

  if (urls.length === 0) {
    throw new Error($('Error in template, the key %s is not found in deploymentTemplateFileUrls'));
  }

  return templateData.definitionTemplates.deploymentTemplateFileUrls[urls[0]];
};

exports.getAllEvents = function (subscription, groupName) {
  var output = through();
  var client = subscription.createResourceClient('createEventsClient');

  client.eventData.listEventsForResourceGroup({
    resourceGroupName: groupName,
    startTime: new Date(Date.now() - eventRetentionPeriodMS),
    endTime: new Date()
  }, function (err, response) {
    if (err) {
      return output.emit('error', err);
    }

    response.eventDataCollection.value.forEach(function (e) {
      output.queue(e);
    });
    output.end();
  });
  return output;
};

exports.getDeploymentLog = function (subscription, name, deploymentName) {
  var output = through();
  var client = subscription.createResourceClient('createResourceManagementClient');

  client.deployments.get(name, deploymentName, function (err, result) {
    if (err) {
      return output.emit('error', err);
    }
    getDeploymentLogs(subscription, result.deployment.properties.correlationId).pipe(output);
  });
  return output;
};

exports.getLastDeploymentLog = function (subscription, name) {
  var output = through();
  var client = subscription.createResourceClient('createResourceManagementClient');

  client.deployments.list(name, { top: 1 }, function (err, response) {
    if (err) { return output.emit('error', err); }
    if (response.deployments.length === 0) {
      output.emit('error', new Error($('Deployment not found')));
    }
    getDeploymentLogs(subscription, response.deployments[0].properties.correlationId).pipe(output);
  });
  return output;
};

exports.normalizeDownloadFileName = function normalizeDownloadFileName(name, file, quiet, confirmer, callback) {
  name = name + '.json';
  var downloadFile = path.resolve(file || name);

  function ensureDirExists(dirname) {
    if (!dirname) {
      return;
    }

    if (utils.pathExistsSync(dirname)) {
      if (!fs.statSync(dirname).isDirectory()) {
        throw new Error(util.format($('Path %s already exists and is not a directory.'), dirname));
      }
      return;
    }

    ensureDirExists(path.dirname(dirname));

    fs.mkdirSync(dirname);
  }

  function normalizeFile() {
    try {
      ensureDirExists(path.dirname(downloadFile));
      if (utils.pathExistsSync(downloadFile) && !quiet) {
        confirmer(
          util.format($('The file %s already exists. Overwrite? [y/n]: '), downloadFile),
            function (confirmed) {
              if (confirmed) {
                callback(null, downloadFile);
              } else {
                callback(null, null);
              }
            }
        );
      } else {
        callback(null, downloadFile);
      }
    } catch (ex) {
      callback(ex);
    }
  }

  function normalizeDirectory() {
    downloadFile = path.join(downloadFile, name);
    normalizeFile();
  }

  if (utils.pathExistsSync(downloadFile) && fs.statSync(downloadFile).isDirectory()) {
    normalizeDirectory();
  } else {
    normalizeFile();
  }
};

exports.warnOnUsingStorage = function (options, log) {
  if (options.storageAccount) {
    log.warn($('Storage account parameter is not used anymore and will be removed soon in a future release.'));
  }
};

function createDeploymentParameters(cli, subscription, resourceGroup, options, _) {
  var templateOptions = [options.galleryTemplate, options.templateFile, options.templateUri];
  var templateOptionsProvided = templateOptions.filter(function (value) { return value !== undefined; }).length;
  if (templateOptionsProvided > 1) {
    throw new Error($('Specify exactly one of the --gallery-template, --template-file, or template-uri options.'));
  } else if (templateOptionsProvided === 0) {
    throw new Error($('One of the --gallery-template, --template-file, or --template-uri options is required.'));
  }

  if (options.parameters && options.parametersFile) {
    throw new Error($('Either --parameters or --parameters-file need to be specified. Not both.'));
  }

  var deploymentParameters;
  if (options.parametersFile) {
    var jsonFile = fs.readFileSync(options.parametersFile, 'utf8');
    deploymentParameters = JSON.parse(utils.stripBOM(jsonFile));
  } else if (options.parameters) {
    deploymentParameters = JSON.parse(options.parameters);
  }

  var templateParameters = { mode: 'Incremental' };
  cli.interaction.withProgress($('Initializing template configurations and parameters'),
    function (log, _) {

      var templateUri = options.templateUri;
      if (!templateUri) {
        var templateContent = getTemplateContent(subscription,
           options.templateFile,
           options.galleryTemplate,
           _);
        templateParameters['template'] = templateContent;
      } else {
        templateParameters['templateLink'] = { uri: templateUri };
        if (options.templateVersion) {
          templateParameters.templateLink.contentVersion = options.templateVersion;
        }
      }

      if (deploymentParameters) {
        templateParameters.parameters = deploymentParameters;
      }

    }, _);
  return templateParameters;
}

var eventRetentionPeriodMS = 89 * 24 * 60 * 60 * 1000; // 89 days in milliseconds

function getDeploymentLogs(subscription, correlationId) {
  var output = through();
  var client = subscription.createResourceClient('createEventsClient');
  client.eventData.listEventsForCorrelationId({
    correlationId: correlationId,
    startTime: new Date(Date.now() - eventRetentionPeriodMS),
    endTime: new Date()
  }, function (err, response) {
    if (err) {
      return output.emit('error', err);
    }
    response.eventDataCollection.value.forEach(function (e) {
      output.queue(e);
    });
    output.end();
  });
  return output;
}

function getTemplateProviders(validationResponse) {
  return __.map(validationResponse.properties.providers, function (provider) {
    return provider.namespace.toLowerCase();
  });
}

function addKnownProviders(subscription) {
  __.each(subscription.registeredResourceNamespaces, function (ns) {
    profile.addKnownResourceNamespace(ns);
  });

  __.each(subscription.registeredProviders, function (ns) {
    profile.addKnownProvider(ns);
  });
}

function getTemplateContent(subscription, templateFile, galleryTemplateName, _) {
  function readTemplateFileContentFromUri(templateFileUri, callback) {
    request(templateFileUri, function (error, response, body) {
      callback(error, body);//need to filter on some errors?
    });
  }

  var templateFileUri;
  if (templateFile) {
    if (validation.isURL(templateFile)) {
      templateFileUri = templateFile;
    }
  } else if (galleryTemplateName) {
    templateFileUri = exports.getGalleryTemplateFile(subscription, galleryTemplateName, _);
  }

  var content = {};
  if (templateFileUri) {
    content = readTemplateFileContentFromUri(templateFileUri, _);
  } else if (templateFile) {
    content = fs.readFileSync(utils.stripBOM(templateFile));
  }

  return JSON.parse(content);
}
<commit_msg>fix a type in the comment<commit_after>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

var __ = require('underscore');
var fs = require('fs');
var path = require('path');
var azure = require('azure');
var request = require('request');
var through = require('through');
var util = require('util');

var knownLocations = require('../location/knownLocations');
var validation = require('../../../util/validation');
var profile = require('../../../util/profile');
var utils = require('../../../util/utils');

var $ = utils.getLocaleString;

/**
* Validate that a given location is valid for group creation
* and prompts for location if not valid or not given.
*
* @param {string} location location requested
* @param {object} log object to print messages to user
* @param {object} interaction the interactor object to prompt with
* @param {function} callback callback received either error or final location
*/
exports.validateLocation = function validateLocation(location, log, interaction, callback) {
  var validLocations = knownLocations.getValidLocationsOfResourceGroup();

  if (location && validLocations.some(function (l) { return utils.ignoreCaseEquals(l, location); })) {
    return callback(null, location);
  }

  if (location) {
    log.info(util.format($('The location %s is not valid'), location));
  }

  log.info($('Choose location: '));
  interaction.choose(validLocations, function (err, locationIndex) {
    callback(err, validLocations[locationIndex]);
  });
};

exports.getGroup = function getGroup(client, name, callback) {
  client.resourceGroups.get(name, function (err, group) {
    if (err) {
      // 404 means doesn't exist
      if (err.statusCode === 404) {
        callback(null, null);
      } else {
        // some other error, let it out
        callback(err);
      }
    } else {
      // No error, group exists
      callback(null, group.resourceGroup);
    }
  });
};

exports.getResource = function getResource(client, resourceGroup, identity, callback) {
  client.resources.get(resourceGroup, identity, function (err, resource) {
    if (err) {
      // 404 means doesn't exist
      if (err.statusCode === 404) {
        callback(null, null);
      } else {
        // some other error, let it out
        callback(err);
      }
    } else {
      // No error, resource exists
      callback(null, resource.resource);
    }
  });
};

exports.createDeployment = function (cli, resourceGroup, name, options, _) {
  var subscription = profile.current.getSubscription(options.subscription);
  var client = subscription.createResourceClient('createResourceManagementClient');

  var templateParameters = createDeploymentParameters(cli, subscription, resourceGroup, options, _);

  //if not provided, derive it from the template file name
  if (!name) {
    var templateName = options.galleryTemplate || options.templateFile || options.templateUri;
    var baseTemplateName = path.basename(templateName);
    //if the file extension is '.json', get rid of it.
    if (utils.stringEndsWith(baseTemplateName, '.json', true)) {
      baseTemplateName = path.basename(baseTemplateName, path.extname(baseTemplateName));
    }
    name = baseTemplateName;
  }

  var result = cli.interaction.withProgress($('Creating a deployment'),
    function (log, _) {
      var validationResponse = client.deployments.validate(resourceGroup, name, templateParameters, _);
      var createResponse = client.deployments.createOrUpdate(resourceGroup, name, templateParameters, _);
      createResponse.requiredProviders = getTemplateProviders(validationResponse);
      return createResponse;
    }, _);

  cli.output.info(util.format($('Created template deployment "%s"'), name));

  // register providers required for new template
  addKnownProviders(subscription);

  cli.interaction.withProgress($('Registering providers'),
    function (log, _) {
      for (var i = 0; i < result.requiredProviders.length; i++) {
        var namespace = result.requiredProviders[i];
        if (!__.contains(profile.knownResourceNamespaces(), namespace)) {
          log.info(util.format($('Registering provider %s'), namespace));
          subscription.registerResourceNamespace(namespace, _);
        }
      }
    },
    _);

  return result.deployment;

};

exports.validateTemplate = function (cli, resourceGroup, options, _) {
  var subscription = profile.current.getSubscription(options.subscription);
  var client = subscription.createResourceClient('createResourceManagementClient');

  var templateParameters = createDeploymentParameters(cli, subscription, resourceGroup, options, _);

  var response = cli.interaction.withProgress($('Validating the template'),
    function (log, _) {
      return client.deployments.validate(resourceGroup, 'fakedDeploymentName', templateParameters, _);
    }, _);

  response.requiredProviders = getTemplateProviders(response);

  return response;
};

exports.getGalleryTemplateFile = function (subscription, galleryTemplateName, _) {
  var galleryClient = utils.createClient('createGalleryClient', new azure.AnonymousCloudCredentials(),
    subscription.galleryEndpointUrl);
  var galleryItem = galleryClient.items.get(galleryTemplateName, _).item;
  return exports.getTemplateDownloadUrl(galleryItem);
};

exports.getTemplateDownloadUrl = function getTemplateDownloadUrl(templateData) {
  var key = templateData.definitionTemplates.defaultDeploymentTemplateId;
  var urls = Object.keys(templateData.definitionTemplates.deploymentTemplateFileUrls)
    .filter(function (url) { return utils.ignoreCaseEquals(key, url); });

  if (urls.length === 0) {
    throw new Error($('Error in template, the key %s is not found in deploymentTemplateFileUrls'));
  }

  return templateData.definitionTemplates.deploymentTemplateFileUrls[urls[0]];
};

exports.getAllEvents = function (subscription, groupName) {
  var output = through();
  var client = subscription.createResourceClient('createEventsClient');

  client.eventData.listEventsForResourceGroup({
    resourceGroupName: groupName,
    startTime: new Date(Date.now() - eventRetentionPeriodMS),
    endTime: new Date()
  }, function (err, response) {
    if (err) {
      return output.emit('error', err);
    }

    response.eventDataCollection.value.forEach(function (e) {
      output.queue(e);
    });
    output.end();
  });
  return output;
};

exports.getDeploymentLog = function (subscription, name, deploymentName) {
  var output = through();
  var client = subscription.createResourceClient('createResourceManagementClient');

  client.deployments.get(name, deploymentName, function (err, result) {
    if (err) {
      return output.emit('error', err);
    }
    getDeploymentLogs(subscription, result.deployment.properties.correlationId).pipe(output);
  });
  return output;
};

exports.getLastDeploymentLog = function (subscription, name) {
  var output = through();
  var client = subscription.createResourceClient('createResourceManagementClient');

  client.deployments.list(name, { top: 1 }, function (err, response) {
    if (err) { return output.emit('error', err); }
    if (response.deployments.length === 0) {
      output.emit('error', new Error($('Deployment not found')));
    }
    getDeploymentLogs(subscription, response.deployments[0].properties.correlationId).pipe(output);
  });
  return output;
};

exports.normalizeDownloadFileName = function normalizeDownloadFileName(name, file, quiet, confirmer, callback) {
  name = name + '.json';
  var downloadFile = path.resolve(file || name);

  function ensureDirExists(dirname) {
    if (!dirname) {
      return;
    }

    if (utils.pathExistsSync(dirname)) {
      if (!fs.statSync(dirname).isDirectory()) {
        throw new Error(util.format($('Path %s already exists and is not a directory.'), dirname));
      }
      return;
    }

    ensureDirExists(path.dirname(dirname));

    fs.mkdirSync(dirname);
  }

  function normalizeFile() {
    try {
      ensureDirExists(path.dirname(downloadFile));
      if (utils.pathExistsSync(downloadFile) && !quiet) {
        confirmer(
          util.format($('The file %s already exists. Overwrite? [y/n]: '), downloadFile),
            function (confirmed) {
              if (confirmed) {
                callback(null, downloadFile);
              } else {
                callback(null, null);
              }
            }
        );
      } else {
        callback(null, downloadFile);
      }
    } catch (ex) {
      callback(ex);
    }
  }

  function normalizeDirectory() {
    downloadFile = path.join(downloadFile, name);
    normalizeFile();
  }

  if (utils.pathExistsSync(downloadFile) && fs.statSync(downloadFile).isDirectory()) {
    normalizeDirectory();
  } else {
    normalizeFile();
  }
};

exports.warnOnUsingStorage = function (options, log) {
  if (options.storageAccount) {
    log.warn($('Storage account parameter is not used anymore and will be removed soon in a future release.'));
  }
};

function createDeploymentParameters(cli, subscription, resourceGroup, options, _) {
  var templateOptions = [options.galleryTemplate, options.templateFile, options.templateUri];
  var templateOptionsProvided = templateOptions.filter(function (value) { return value !== undefined; }).length;
  if (templateOptionsProvided > 1) {
    throw new Error($('Specify exactly one of the --gallery-template, --template-file, or template-uri options.'));
  } else if (templateOptionsProvided === 0) {
    throw new Error($('One of the --gallery-template, --template-file, or --template-uri options is required.'));
  }

  if (options.parameters && options.parametersFile) {
    throw new Error($('Either --parameters or --parameters-file need to be specified. Not both.'));
  }

  var deploymentParameters;
  if (options.parametersFile) {
    var jsonFile = fs.readFileSync(options.parametersFile, 'utf8');
    deploymentParameters = JSON.parse(utils.stripBOM(jsonFile));
  } else if (options.parameters) {
    deploymentParameters = JSON.parse(options.parameters);
  }

  var templateParameters = { mode: 'Incremental' };
  cli.interaction.withProgress($('Initializing template configurations and parameters'),
    function (log, _) {

      var templateUri = options.templateUri;
      if (!templateUri) {
        var templateContent = getTemplateContent(subscription,
           options.templateFile,
           options.galleryTemplate,
           _);
        templateParameters['template'] = templateContent;
      } else {
        templateParameters['templateLink'] = { uri: templateUri };
        if (options.templateVersion) {
          templateParameters.templateLink.contentVersion = options.templateVersion;
        }
      }

      if (deploymentParameters) {
        templateParameters.parameters = deploymentParameters;
      }

    }, _);
  return templateParameters;
}

var eventRetentionPeriodMS = 89 * 24 * 60 * 60 * 1000; // 89 days in milliseconds

function getDeploymentLogs(subscription, correlationId) {
  var output = through();
  var client = subscription.createResourceClient('createEventsClient');
  client.eventData.listEventsForCorrelationId({
    correlationId: correlationId,
    startTime: new Date(Date.now() - eventRetentionPeriodMS),
    endTime: new Date()
  }, function (err, response) {
    if (err) {
      return output.emit('error', err);
    }
    response.eventDataCollection.value.forEach(function (e) {
      output.queue(e);
    });
    output.end();
  });
  return output;
}

function getTemplateProviders(validationResponse) {
  return __.map(validationResponse.properties.providers, function (provider) {
    return provider.namespace.toLowerCase();
  });
}

function addKnownProviders(subscription) {
  __.each(subscription.registeredResourceNamespaces, function (ns) {
    profile.addKnownResourceNamespace(ns);
  });

  __.each(subscription.registeredProviders, function (ns) {
    profile.addKnownProvider(ns);
  });
}

function getTemplateContent(subscription, templateFile, galleryTemplateName, _) {
  function readTemplateFileContentFromUri(templateFileUri, callback) {
    request(templateFileUri, function (error, response, body) {
      callback(error, body);//need to filter on some errors?
    });
  }

  var templateFileUri;
  if (templateFile) {
    if (validation.isURL(templateFile)) {
      templateFileUri = templateFile;
    }
  } else if (galleryTemplateName) {
    templateFileUri = exports.getGalleryTemplateFile(subscription, galleryTemplateName, _);
  }

  var content = {};
  if (templateFileUri) {
    content = readTemplateFileContentFromUri(templateFileUri, _);
  } else if (templateFile) {
    content = fs.readFileSync(utils.stripBOM(templateFile));
  }

  return JSON.parse(content);
}
<|endoftext|>