<commit_before>function handleDragStart(e) {
  this.style.opacity = '0.4';  // this / e.target is the source node.
}

function handleDragStart(e) {
  this.style.opacity = '0.4';  // this / e.target is the source node.
}

function handleDragOver(e) {
  if (e.preventDefault) {
    e.preventDefault(); // Necessary. Allows us to drop.
  }

  e.dataTransfer.dropEffect = 'move';  // See the section on the DataTransfer object.

  return false;
}

function handleDragEnter(e) {
  // this / e.target is the current hover target.
  this.classList.add('over');
}

function handleDragLeave(e) {
  this.classList.remove('over');  // this / e.target is previous target element.
}

function handleDrop(e) {
  // this / e.target is current target element.

  if (e.stopPropagation) {
    e.stopPropagation(); // stops the browser from redirecting.
  }

  // See the section on the DataTransfer object.

  return false;
}

function handleDragEnd(e) {
  // this/e.target is the source node.

  [].forEach.call(cols, function (col) {
    col.classList.remove('over');
  });
}

function handleDragStart(e) {
  // Target (this) element is the source node.
  this.style.opacity = '0.4';

  dragSrcEl = this;

  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}

function handleDrop(e) {
  // this/e.target is current target element.

  if (e.stopPropagation) {
    e.stopPropagation(); // Stops some browsers from redirecting.
  }

  // Don't do anything if dropping the same column we're dragging.
  if (dragSrcEl != this) {
    // Set the source column's HTML to the HTML of the column we dropped on.
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text/html');
  }

  return false;
}

function dropInDetector(e){
  if (e.stopPropagation) {
    e.stopPropagation(); // Stops some browsers from redirecting.
  }

  this.appendChild(dragSrcEl.cloneNode(true));

  var cols = document.querySelectorAll('#columns .column');
  [].forEach.call(cols, function(col) {
    col.addEventListener('dragstart', handleDragStart, false);
    col.addEventListener('dragenter', handleDragEnter, false)
    col.addEventListener('dragover', handleDragOver, false);
    col.addEventListener('dragleave', handleDragLeave, false);
    col.addEventListener('drop', handleDrop, false);
    col.addEventListener('dragend', handleDragEnd, false);
  });

  return false;
}
<commit_msg>removed opacity changes<commit_after>function handleDragStart(e) {
  this.style.opacity = '0.4';  // this / e.target is the source node.
}

function handleDragStart(e) {
  this.style.opacity = '0.4';  // this / e.target is the source node.
}

function handleDragOver(e) {
  if (e.preventDefault) {
    e.preventDefault(); // Necessary. Allows us to drop.
  }

  e.dataTransfer.dropEffect = 'move';  // See the section on the DataTransfer object.

  return false;
}

function handleDragEnter(e) {
  // this / e.target is the current hover target.
  this.classList.add('over');
}

function handleDragLeave(e) {
  this.classList.remove('over');  // this / e.target is previous target element.
}

function handleDrop(e) {
  // this / e.target is current target element.

  if (e.stopPropagation) {
    e.stopPropagation(); // stops the browser from redirecting.
  }

  // See the section on the DataTransfer object.

  return false;
}

function handleDragEnd(e) {
  // this/e.target is the source node.

  [].forEach.call(cols, function (col) {
    col.classList.remove('over');
  });
}

function handleDragStart(e) {

  dragSrcEl = this;

  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}

function handleDrop(e) {
  // this/e.target is current target element.

  if (e.stopPropagation) {
    e.stopPropagation(); // Stops some browsers from redirecting.
  }

  // Don't do anything if dropping the same column we're dragging.
  if (dragSrcEl != this) {
    // Set the source column's HTML to the HTML of the column we dropped on.
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text/html');
  }

  return false;
}

function dropInDetector(e){
  if (e.stopPropagation) {
    e.stopPropagation(); // Stops some browsers from redirecting.
  }

  this.appendChild(dragSrcEl.cloneNode(true));

  var cols = document.querySelectorAll('#columns .column');
  [].forEach.call(cols, function(col) {
    col.addEventListener('dragstart', handleDragStart, false);
    col.addEventListener('dragenter', handleDragEnter, false)
    col.addEventListener('dragover', handleDragOver, false);
    col.addEventListener('dragleave', handleDragLeave, false);
    col.addEventListener('drop', handleDrop, false);
    col.addEventListener('dragend', handleDragEnd, false);
  });

  return false;
}
<|endoftext|>


<commit_before>module.exports = function (sails) {

	/**
	 * Module dependencies.
	 */

	var util		= require( '../../util' );

	/**
	 * Global access to middleware
	 * (useful as helpers)
	 */

	 sails._mixinLocals = _mixinLocals;
	 sails._mixinResError = _mixinResError;
	 sails._mixinServerMetadata = _mixinServerMetadata;
	 sails._mixinReqQualifiers = _mixinReqQualifiers;

	/**
	 * Extend middleware req/res for this route w/ new methods / qualifiers.
	 */

	return {

		/**
		 * Bind req/res syntactic sugar before apply any app-level routes
		 */

		initialize: function (cb) {

			var self = this;

			sails.on('router:before', function () {

				sails.router.bind('/*', function addSugarMethods (req, res, next) {

					_mixinLocals(req,res);
					_mixinResError(req,res);
					_mixinServerMetadata(req,res);

					// Only apply HTTP middleware if it makes sense
					// (i.e. if this is an HTTP request)
					if (req.protocol === 'http' || req.protocol === 'https') {
						_mixinReqQualifiers(req,res);
					}

					next();
				});

				self.ready = true;

			});

			// Logic to inject before running each middleware
			sails.on('router:route', function (requestState) {
				var req = requestState.req;
				var res = requestState.res;
				var next = requestState.next;

				mixinSugar(req,res);
			});

			cb();
		}
	};

	/**
	 * Always share some basic metadata with views
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function _mixinLocals (req,res) {

		res.locals({
			_		: util,
			util	: util,
			session	: req.session,
			title	: sails.config.appName + (req.param('action') ? (' | ' + util.str.capitalize(req.param('action'))) : ''),
			req		: req,
			res		: res,
			sails	: sails
		});
	}

	/**
	 * Override the behavior of res.error to better handle errors
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function _mixinResError (req,res) {

		res.error = function respondWithError (err, statusCode) {

			// Argument defaults
			err = err || 'Unexpected error occurred.';
			statusCode = statusCode || 500;

			if (err instanceof Error) {
				var msg = sails.config.environment === 'development' ? err.stack : err.toString();
				return res.send(msg, statusCode);
			}

			if (util.isObject(err)) {
				return res.json(err, statusCode);
			}

			return res.send(err, statusCode);

		};
	}

	/**
	 * Some syntactic sugar
	 * req.params.all();
	 * (this is applied per-route, not per request)
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function mixinSugar (req,res) {

		// Make sure `id` is omitted if it's undefined
		// (since action blueprint routes name an optional :id)
		if ( typeof req.param('id') === 'undefined' ) {
			delete req.params.id;
		}

		// Combines parameters from the query string, and encoded request body
		// to compose a monolithic object of named parameters, irrespective of source
		var queryParams = util.clone(req.query) || {};
		var bodyParams = util.clone(req.body) || {};
		var allParams = util.extend({}, queryParams, bodyParams);

		// Mixin route params
		util.each(Object.keys(req.params), function (paramName) {
			allParams[paramName] = req.params[paramName];
		});

		// Define a new non-enuerable function: req.params.all()
		// (but only if an `:all` route parameter doesn't exist)
		if (!req.params.all) {
			Object.defineProperty(req.params, 'all', {
				value: function getAllParams () {
					return allParams;
				}
			});
		}
		sails.log('req.params', req.params);

		// TODO: make req.params() do the same thing as req.params.all() ??

	}

	/**
	 * Mix in convenience flags about this request
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function _mixinReqQualifiers (req,res) {
		var accept = req.get('Accept') || '';

		// Flag indicating whether HTML was explicitly mentioned in the Accepts header
		req.explicitlyAcceptsHTML = (accept.indexOf('html') !== -1);

		// Flag indicating whether a request would like to receive a JSON response
		req.wantsJSON =	req.xhr;
		req.wantsJSON =	req.wantsJSON || !req.explicitlyAcceptsHTML;
		req.wantsJSON =	req.wantsJSON || ( req.is('json') && req.get('Accept') );

		// Legacy support for Sails 0.8.x
		req.isAjax = req.xhr;
		req.isJson = req.header('content-type') === 'application/json';
		req.acceptJson = req.header('Accept') === 'application/json';
		req.isJsony = req.isJson || req.acceptJson;
	}

	/**
	 * Host, port, etc.
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function _mixinServerMetadata (req,res) {

		// Access to server port, if available
		req.port = req.port;

		// Add access to full base url for convenience
		req.baseUrl = req.protocol + '://' + req.host + (req.port == 80 || req.port == 443 ? '' : ':' + req.port);

		// Legacy support for Sails 0.8.x
		req.rawHost = req.host;
		req.rootUrl = req.baseUrl;
		req.baseurl = req.baseUrl;
	}

};
<commit_msg>Removed log<commit_after>module.exports = function (sails) {

	/**
	 * Module dependencies.
	 */

	var util		= require( '../../util' );

	/**
	 * Global access to middleware
	 * (useful as helpers)
	 */

	 sails._mixinLocals = _mixinLocals;
	 sails._mixinResError = _mixinResError;
	 sails._mixinServerMetadata = _mixinServerMetadata;
	 sails._mixinReqQualifiers = _mixinReqQualifiers;

	/**
	 * Extend middleware req/res for this route w/ new methods / qualifiers.
	 */

	return {

		/**
		 * Bind req/res syntactic sugar before apply any app-level routes
		 */

		initialize: function (cb) {

			var self = this;

			sails.on('router:before', function () {

				sails.router.bind('/*', function addSugarMethods (req, res, next) {

					_mixinLocals(req,res);
					_mixinResError(req,res);
					_mixinServerMetadata(req,res);

					// Only apply HTTP middleware if it makes sense
					// (i.e. if this is an HTTP request)
					if (req.protocol === 'http' || req.protocol === 'https') {
						_mixinReqQualifiers(req,res);
					}

					next();
				});

				self.ready = true;

			});

			// Logic to inject before running each middleware
			sails.on('router:route', function (requestState) {
				var req = requestState.req;
				var res = requestState.res;
				var next = requestState.next;

				mixinSugar(req,res);
			});

			cb();
		}
	};

	/**
	 * Always share some basic metadata with views
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function _mixinLocals (req,res) {

		res.locals({
			_		: util,
			util	: util,
			session	: req.session,
			title	: sails.config.appName + (req.param('action') ? (' | ' + util.str.capitalize(req.param('action'))) : ''),
			req		: req,
			res		: res,
			sails	: sails
		});
	}

	/**
	 * Override the behavior of res.error to better handle errors
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function _mixinResError (req,res) {

		res.error = function respondWithError (err, statusCode) {

			// Argument defaults
			err = err || 'Unexpected error occurred.';
			statusCode = statusCode || 500;

			if (err instanceof Error) {
				var msg = sails.config.environment === 'development' ? err.stack : err.toString();
				return res.send(msg, statusCode);
			}

			if (util.isObject(err)) {
				return res.json(err, statusCode);
			}

			return res.send(err, statusCode);

		};
	}

	/**
	 * Some syntactic sugar
	 * req.params.all();
	 * (this is applied per-route, not per request)
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function mixinSugar (req,res) {

		// Make sure `id` is omitted if it's undefined
		// (since action blueprint routes name an optional :id)
		if ( typeof req.param('id') === 'undefined' ) {
			delete req.params.id;
		}

		// Combines parameters from the query string, and encoded request body
		// to compose a monolithic object of named parameters, irrespective of source
		var queryParams = util.clone(req.query) || {};
		var bodyParams = util.clone(req.body) || {};
		var allParams = util.extend({}, queryParams, bodyParams);

		// Mixin route params
		util.each(Object.keys(req.params), function (paramName) {
			allParams[paramName] = req.params[paramName];
		});

		// Define a new non-enuerable function: req.params.all()
		// (but only if an `:all` route parameter doesn't exist)
		if (!req.params.all) {
			Object.defineProperty(req.params, 'all', {
				value: function getAllParams () {
					return allParams;
				}
			});
		}

		// TODO: make req.params() do the same thing as req.params.all() ??

	}

	/**
	 * Mix in convenience flags about this request
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function _mixinReqQualifiers (req,res) {
		var accept = req.get('Accept') || '';

		// Flag indicating whether HTML was explicitly mentioned in the Accepts header
		req.explicitlyAcceptsHTML = (accept.indexOf('html') !== -1);

		// Flag indicating whether a request would like to receive a JSON response
		req.wantsJSON =	req.xhr;
		req.wantsJSON =	req.wantsJSON || !req.explicitlyAcceptsHTML;
		req.wantsJSON =	req.wantsJSON || ( req.is('json') && req.get('Accept') );

		// Legacy support for Sails 0.8.x
		req.isAjax = req.xhr;
		req.isJson = req.header('content-type') === 'application/json';
		req.acceptJson = req.header('Accept') === 'application/json';
		req.isJsony = req.isJson || req.acceptJson;
	}

	/**
	 * Host, port, etc.
	 *
	 * @param {Request} req
	 * @param {Response} res
	 * @api private
	 */

	function _mixinServerMetadata (req,res) {

		// Access to server port, if available
		req.port = req.port;

		// Add access to full base url for convenience
		req.baseUrl = req.protocol + '://' + req.host + (req.port == 80 || req.port == 443 ? '' : ':' + req.port);

		// Legacy support for Sails 0.8.x
		req.rawHost = req.host;
		req.rootUrl = req.baseUrl;
		req.baseurl = req.baseUrl;
	}

};
<|endoftext|>


<commit_before>// Copyright 2011 Joyent, Inc.  All rights reserved.

var assert = require('assert');

var restify = require('restify');
var sprintf = require('sprintf').sprintf;

///--- Globals

var HttpCodes = restify.HttpCodes;
var RestCodes = restify.RestCodes;
var log = restify.log;
var newError = restify.newError;

var CA_FMT = '/ca/customers/%s';
var INST_BASE_FMT = CA_FMT + '/instrumentations';
var INST_FMT = INST_BASE_FMT + '/%s';
var INST_CLONE_FMT = INST_FMT + '/clone';
var RAW_FMT = INST_FMT + '/value/raw';
var HEATMAP_IMG_FMT = INST_FMT + '/value/heatmap/image';
var HEATMAP_DETAILS_FMT = INST_FMT + '/value/heatmap/image';

///--- Exported CA Client

/**
 * Constructor
 *
 * Note that in options you can pass in any parameters that the restify
 * RestClient constructor takes (for example retry/backoff settings).
 *
 * @param {Object} options
 *                  - url {String} CA location.
 *
 */
function CA(options) {
  if (!options) throw new TypeError('options required');
  if ((options.uri && options.url) ||
      !(options.uri || options.url))
    throw new TypeError('One of options.uri, options.url required');

  if (options.uri)
    options.url = options.uri;
  if (options.logLevel)
    log.level(options.logLevel);
  if (!options.version)
    options.version = 'ca/0.1.6';
  this.client = restify.createClient(options);
}

/**
 * Does a listing of the "root" CA endpoint.
 *
 * This hoss gives you the "schema" that CA supports.
 *
 * @param {String} customer a CAPI customer uuid.
 * @param {Function} callback of the form f(err, schema).
 */
CA.prototype.listSchema = function(customer, callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(CA_FMT, customer)
  };
  return this.client.get(request, function(err, schema) {
    if (err) return callback(self._translateError(err));

    return callback(null, schema);
  });
};
CA.prototype.getSchema = CA.prototype.listSchema;
CA.prototype.list = CA.prototype.listSchema;
CA.prototype.describe = CA.prototype.listSchema;

/**
 * Lists all instrumentations created for a customer.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {Function} callback of the form f(err, instrumentations).
 */
CA.prototype.listInstrumentations = function(customer, callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_BASE_FMT, customer)
  };
  return this.client.get(request, function(err, instrumentations) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentations);
  });
};

/**
 * Creates a new CA instrumentation.
 *
 * Refer to the CA documentation for an explanation of what goes in params.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {Object} params the intstrumentation parameters.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.createInstrumentation = function(customer, params, callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!params || typeof(params) !== 'object')
    throw new TypeError('params is required(object)');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_BASE_FMT, customer),
    body: params
  };
  return this.client.post(request, function(err, instrumentation) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentation);
  });
};

/**
 * Clones a CA instrumentation.
 *
 * Refer to the CA documentation for an explanation of what goes in params.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {Object} params the intstrumentation parameters.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.cloneInstrumentation = function(customer, id, params, callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!id) throw new TypeError('id is required');
  if (typeof(params) === 'function') {
    callback = params;
    params = {};
  }
  if (!params || typeof(params) !== 'object')
    throw new TypeError('params must be an object');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_CLONE_FMT, customer, id),
    body: params
  };
  return this.client.post(request, function(err, instrumentation) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentation);
  });
};

/**
 * Retrieves a single instrumentation by CA instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.getInstrumentation = function(customer,
                                           instrumentation,
                                           callback) {

  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_FMT, customer, instrumentation)
  };
  return this.client.get(request, function(err, instrumentation) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentation);
  });
};

/**
 * Retrieves a single "raw" instrumentation by CA instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.getInstrumentationValueRaw = function(customer,
                                                   instrumentation,
                                                   callback) {

  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(RAW_FMT, customer, instrumentation)
  };
  return this.client.get(request, function(err, instrumentation) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentation);
  });
};

/**
 * Retrieves an instrumentation heatmap image from CA by instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Object} params see the CA docs.
 * @param {Function} callback of the form f(err, heatmap).
 */
CA.prototype.getHeatmap = function(customer,
                                   instrumentation,
                                   params,
                                   callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (typeof(params) !== 'object') {
    callback = params;
    params = {};
  }
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(HEATMAP_IMG_FMT, customer, instrumentation),
    query: params
  };
  return this.client.get(request, function(err, heatmap) {
    if (err) return callback(self._translateError(err));

    return callback(null, heatmap);
  });
};
CA.prototype.getInstrumentationHeatmap = CA.prototype.getHeatmap;
CA.prototype.getInstrumentationHeatmapImage = CA.prototype.getHeatmap;

/**
 * Retrieves an instrumentation heatmap detail from CA by instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Object} params see the CA docs.
 * @param {Function} cb of the form f(err, details).
 */
CA.prototype.getHeatmapDetails = function(customer,
                                          instrumentation,
                                          params,
                                          cb) {
  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (typeof(params) !== 'object') {
    callback = params;
    params = {};
  }
  if (!cb || typeof(cb) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(HEATMAP_DETAILS_FMT, customer, instrumentation),
    query: params
  };
  return this.client.get(request, function(err, details) {
    if (err) return cb(self._translateError(err));

    return cb(null, details);
  });
};
CA.prototype.getInstrumentationHeatmapDetails = CA.prototype.getHeatmapDetails;

/**
 * Deletes an instrumentation from CA by instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.deleteInstrumentation = function(customer,
                                              instrumentation,
                                              callback) {

  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_FMT, customer, instrumentation)
  };
  return this.client.del(request, function(err) {
    if (err) return callback(self._translateError(err));

    return callback();
  });
};
CA.prototype.destroyInstrumentation = CA.prototype.deleteInstrumentation;

///--- Private methods

CA.prototype._translateError = function(err) {
  assert.ok(err);
  if (!err.details || !err.details.object || !err.details.object.error) {
    // Don't send back the default "unknown error occurred" message here...
    err.message = err.restCode;
    return err;
  }

  var _err = err.details.object.error;

  var message = null;
  var status = null;

  switch (_err.code) {
  case 'ECA_INVAL':
  case 'ECA_EXISTS':
  case 'ECA_INCOMPAT':
    status = HttpCodes.Conflict;
    code = RestCodes.InvalidArgument;
    message = _err.message;
    break;
  case 'ECA_NOENT':
    status = HttpCodes.NotFound;
    code = RestCodes.NotFound;
    message = 'Resource not found.';
    break;
  default:
    status = HttpCodes.InternalError;
    code = RestCodes.UnknownError;
    message = null;
    break;
  }

  return newError({
    httpCode: status,
    restCode: code,
    message: message,
    error: err
  });
};

module.exports = CA;
<commit_msg>PUBAPI-256 CloudAPI is returning a value/heatmap/image payload on value/heatmap/details request.<commit_after>// Copyright 2011 Joyent, Inc.  All rights reserved.

var assert = require('assert');

var restify = require('restify');
var sprintf = require('sprintf').sprintf;

///--- Globals

var HttpCodes = restify.HttpCodes;
var RestCodes = restify.RestCodes;
var log = restify.log;
var newError = restify.newError;

var CA_FMT = '/ca/customers/%s';
var INST_BASE_FMT = CA_FMT + '/instrumentations';
var INST_FMT = INST_BASE_FMT + '/%s';
var INST_CLONE_FMT = INST_FMT + '/clone';
var RAW_FMT = INST_FMT + '/value/raw';
var HEATMAP_IMG_FMT = INST_FMT + '/value/heatmap/image';
var HEATMAP_DETAILS_FMT = INST_FMT + '/value/heatmap/details';

///--- Exported CA Client

/**
 * Constructor
 *
 * Note that in options you can pass in any parameters that the restify
 * RestClient constructor takes (for example retry/backoff settings).
 *
 * @param {Object} options
 *                  - url {String} CA location.
 *
 */
function CA(options) {
  if (!options) throw new TypeError('options required');
  if ((options.uri && options.url) ||
      !(options.uri || options.url))
    throw new TypeError('One of options.uri, options.url required');

  if (options.uri)
    options.url = options.uri;
  if (options.logLevel)
    log.level(options.logLevel);
  if (!options.version)
    options.version = 'ca/0.1.6';
  this.client = restify.createClient(options);
}

/**
 * Does a listing of the "root" CA endpoint.
 *
 * This hoss gives you the "schema" that CA supports.
 *
 * @param {String} customer a CAPI customer uuid.
 * @param {Function} callback of the form f(err, schema).
 */
CA.prototype.listSchema = function(customer, callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(CA_FMT, customer)
  };
  return this.client.get(request, function(err, schema) {
    if (err) return callback(self._translateError(err));

    return callback(null, schema);
  });
};
CA.prototype.getSchema = CA.prototype.listSchema;
CA.prototype.list = CA.prototype.listSchema;
CA.prototype.describe = CA.prototype.listSchema;

/**
 * Lists all instrumentations created for a customer.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {Function} callback of the form f(err, instrumentations).
 */
CA.prototype.listInstrumentations = function(customer, callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_BASE_FMT, customer)
  };
  return this.client.get(request, function(err, instrumentations) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentations);
  });
};

/**
 * Creates a new CA instrumentation.
 *
 * Refer to the CA documentation for an explanation of what goes in params.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {Object} params the intstrumentation parameters.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.createInstrumentation = function(customer, params, callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!params || typeof(params) !== 'object')
    throw new TypeError('params is required(object)');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_BASE_FMT, customer),
    body: params
  };
  return this.client.post(request, function(err, instrumentation) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentation);
  });
};

/**
 * Clones a CA instrumentation.
 *
 * Refer to the CA documentation for an explanation of what goes in params.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {Object} params the intstrumentation parameters.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.cloneInstrumentation = function(customer, id, params, callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!id) throw new TypeError('id is required');
  if (typeof(params) === 'function') {
    callback = params;
    params = {};
  }
  if (!params || typeof(params) !== 'object')
    throw new TypeError('params must be an object');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_CLONE_FMT, customer, id),
    body: params
  };
  return this.client.post(request, function(err, instrumentation) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentation);
  });
};

/**
 * Retrieves a single instrumentation by CA instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.getInstrumentation = function(customer,
                                           instrumentation,
                                           callback) {

  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_FMT, customer, instrumentation)
  };
  return this.client.get(request, function(err, instrumentation) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentation);
  });
};

/**
 * Retrieves a single "raw" instrumentation by CA instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.getInstrumentationValueRaw = function(customer,
                                                   instrumentation,
                                                   callback) {

  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(RAW_FMT, customer, instrumentation)
  };
  return this.client.get(request, function(err, instrumentation) {
    if (err) return callback(self._translateError(err));

    return callback(null, instrumentation);
  });
};

/**
 * Retrieves an instrumentation heatmap image from CA by instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Object} params see the CA docs.
 * @param {Function} callback of the form f(err, heatmap).
 */
CA.prototype.getHeatmap = function(customer,
                                   instrumentation,
                                   params,
                                   callback) {
  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (typeof(params) !== 'object') {
    callback = params;
    params = {};
  }
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(HEATMAP_IMG_FMT, customer, instrumentation),
    query: params
  };
  return this.client.get(request, function(err, heatmap) {
    if (err) return callback(self._translateError(err));

    return callback(null, heatmap);
  });
};
CA.prototype.getInstrumentationHeatmap = CA.prototype.getHeatmap;
CA.prototype.getInstrumentationHeatmapImage = CA.prototype.getHeatmap;

/**
 * Retrieves an instrumentation heatmap detail from CA by instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Object} params see the CA docs.
 * @param {Function} cb of the form f(err, details).
 */
CA.prototype.getHeatmapDetails = function(customer,
                                          instrumentation,
                                          params,
                                          cb) {
  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (typeof(params) !== 'object') {
    callback = params;
    params = {};
  }
  if (!cb || typeof(cb) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(HEATMAP_DETAILS_FMT, customer, instrumentation),
    query: params
  };
  return this.client.get(request, function(err, details) {
    if (err) return cb(self._translateError(err));

    return cb(null, details);
  });
};
CA.prototype.getInstrumentationHeatmapDetails = CA.prototype.getHeatmapDetails;

/**
 * Deletes an instrumentation from CA by instrumentation id.
 *
 * @param {String} customer the CAPI customer uuid.
 * @param {String} instrumentation the intstrumentation id.
 * @param {Function} callback of the form f(err, instrumentation).
 */
CA.prototype.deleteInstrumentation = function(customer,
                                              instrumentation,
                                              callback) {

  if (!customer) throw new TypeError('customer is required');
  if (!instrumentation) throw new TypeError('instrumentation is required');
  if (!callback || typeof(callback) !== 'function')
    throw new TypeError('callback is required (function)');

  var self = this;
  var request = {
    path: sprintf(INST_FMT, customer, instrumentation)
  };
  return this.client.del(request, function(err) {
    if (err) return callback(self._translateError(err));

    return callback();
  });
};
CA.prototype.destroyInstrumentation = CA.prototype.deleteInstrumentation;

///--- Private methods

CA.prototype._translateError = function(err) {
  assert.ok(err);
  if (!err.details || !err.details.object || !err.details.object.error) {
    // Don't send back the default "unknown error occurred" message here...
    err.message = err.restCode;
    return err;
  }

  var _err = err.details.object.error;

  var message = null;
  var status = null;

  switch (_err.code) {
  case 'ECA_INVAL':
  case 'ECA_EXISTS':
  case 'ECA_INCOMPAT':
    status = HttpCodes.Conflict;
    code = RestCodes.InvalidArgument;
    message = _err.message;
    break;
  case 'ECA_NOENT':
    status = HttpCodes.NotFound;
    code = RestCodes.NotFound;
    message = 'Resource not found.';
    break;
  default:
    status = HttpCodes.InternalError;
    code = RestCodes.UnknownError;
    message = null;
    break;
  }

  return newError({
    httpCode: status,
    restCode: code,
    message: message,
    error: err
  });
};

module.exports = CA;
<|endoftext|>


<commit_before>var fs          = require('fs'),
    PNG         = require('node-png').PNG,
    domA        = {},
    domB        = {},
    imgA        = [],
    imgB        = [],
    debug       = false,
    config      = {
        blur:               !debug,
        movingPixels:       !debug,
        sizeDiff:           debug ? 0 : 1,
        threshold:          debug ? 0 : 15,
        cleanup:            !debug
    },
    diff;

function comparePixels(a, b) {
    var i = 0,
        length = a.length;

    if (a === b) return true;

    for (; i < length; i++) {
        if (Math.abs(b[i] - a[i]) > config.threshold) {
            return false;
        }
    }

    return true;
}

function getPixel(png, x, y) {

    if (config.blur) {
        return getPixelBlur(png, x, y);
    }

    var idx = (y * png.width + x) << 2;

    return [
        png.data[idx],
        png.data[idx + 1],
        png.data[idx + 2]
    ]
}

function getPixelBlur(png, x, y) {
    var width   = png.width,
        idx     = (y * width + x) << 2,
        idxT    = ((y - 1) * width + x) << 2,
        idxL    = (y * width + (x - 1)) << 2,
        idxR    = (y * width + (x + 1)) << 2,
        idxB    = ((y + 1) * width + x) << 2,
        idxTL   = ((y - 1) * width + (x - 1)) << 2,
        idxTR   = ((y - 1) * width + (x + 1)) << 2,
        idxBL   = ((y + 1) * width + (x - 1)) << 2,
        idxBR   = ((y + 1) * width + (x + 1)) << 2;

    return [
        Math.round((
            png.data[idx] + png.data[idxT] + png.data[idxL] + png.data[idxR] + png.data[idxB] +
            png.data[idxTL] + png.data[idxTR] + png.data[idxBL] + png.data[idxBR]
        ) / 9),
        Math.round((
            png.data[idx + 1] + png.data[idxT + 1] + png.data[idxL + 1] + png.data[idxR + 1] + png.data[idxB + 1] +
            png.data[idxTL + 1] + png.data[idxTR + 1] + png.data[idxBL + 1] + png.data[idxBR + 1]
        ) / 9),
        Math.round((
            png.data[idx + 2] + png.data[idxT + 2] + png.data[idxL + 2] + png.data[idxR + 2] + png.data[idxB + 2] +
            png.data[idxTL + 2] + png.data[idxTR + 2] + png.data[idxBL + 2] + png.data[idxBR + 2]
        ) / 9)
    ]
}

function compareSlices(a, b) {
    var imgAWidth   = imgA.width,
        imgAHeight  = imgA.height,
        imgBWidth   = imgB.width,
        imgBHeight  = imgB.height,
        aTop        = Math.max(a.offset.top - 1, 0),
        aLeft       = Math.max(a.offset.left - 1, 0),
        aWidth      = Math.min(a.offset.width + 2, imgAWidth - aLeft),
        aHeight     = Math.min(a.offset.height + 2, imgAHeight - aTop),
        bTop        = Math.max(b.offset.top - 1, 0),
        bLeft       = Math.max(b.offset.left - 1, 0),
        bWidth      = Math.min(b.offset.width + 2, imgBWidth - bLeft),
        bHeight     = Math.min(b.offset.height + 2, imgBHeight - bTop),
        width       = Math.min(aWidth, bWidth) - 1,
        height      = Math.min(aHeight, bHeight) - 1,
        x,
        y,
        srcPixel,
        bLeftX,
        bTopY;

    for (y = 1; y < height; y++) {
        for (x = 1; x < width; x++) {
            srcPixel = getPixel(imgA, aLeft + x, aTop + y);
            bLeftX = bLeft + x;
            bTopY = bTop + y;

            if (config.movingPixels) {
                if (!(comparePixels(srcPixel, getPixel(imgB, bLeftX, bTopY)) ||
                    comparePixels(srcPixel, getPixel(imgB, bLeftX, bTopY - 1)) ||
                    comparePixels(srcPixel, getPixel(imgB, bLeftX, bTopY + 1)) ||
                    comparePixels(srcPixel, getPixel(imgB, bLeftX + 1, bTopY)) ||
                    comparePixels(srcPixel, getPixel(imgB, bLeftX - 1, bTopY)) )) {
                    return false;
                }
            } else {
                if (!(comparePixels(srcPixel, getPixel(imgB, bLeftX, bTopY)))) {
                    return false;
                }
            }
        }
    }

    return true;
}

function isMatchingVisually(a, b) {
    var isVisibleA = a.offset.width > 0 && a.offset.height > 0,
        isVisibleB = b.offset.width > 0 && b.offset.height > 0;

    if (isVisibleA && isVisibleB && isMatchingSize(a, b)) {
        return compareSlices(a, b);
    } else {
        return false;
    }
}

function compare(a, b) {
    var nameMatching,
        offsetMatching,
        visualMatching,
        styleMatching,
        differences = [],
        diffObj = {},
        nodesLengthA,
        nodesLengthB,
        i = 0,
        length;

    if (a && b) {

        nameMatching = isMatchingName(a, b);
        offsetMatching = isMatchingOffset(a, b);
        styleMatching = isMatchingStyle(a, b);

        if (!(nameMatching && offsetMatching && styleMatching)) {

            if (!nameMatching) { differences.push('NOT_MATCHING_NAME'); }

            if (!offsetMatching && a && b) {
                if (a.offset.width > b.offset.width)    { differences.push('OFFSET_WIDTH_LOWER'); }
                if (a.offset.width < b.offset.width)    { differences.push('OFFSET_WIDTH_HIGHER'); }
                if (a.offset.height > b.offset.height ) { differences.push('OFFSET_HEIGHT_LOWER'); }
                if (a.offset.height < b.offset.height ) { differences.push('OFFSET_HEIGHT_HIGHER'); }
                if (a.offset.left > b.offset.left)      { differences.push('OFFSET_X_LOWER'); }
                if (a.offset.left < b.offset.left)      { differences.push('OFFSET_X_HIGHER'); }
                if (a.offset.top > b.offset.top)        { differences.push('OFFSET_Y_LOWER'); }
                if (a.offset.top < b.offset.top)        { differences.push('OFFSET_Y_HIGHER'); }
            }
        }

        if (!styleMatching) {
            differences.push('NOT_MATCHING_STYLE');
        }

        if (styleMatching && isMatchingSize(a, b)) {
            visualMatching = isMatchingVisually(a, b);
        } else {
            visualMatching = false;
        }

        if(!visualMatching) {
            differences.push('NOT_MATCHING_VISUALLY');
        }
    }

    if (a) {
        diffObj.a = {
            name:   a.name,
            path:   a.path,
            offset: a.offset,
            style:  a.style
        };
    } else {
        differences.push('NODE_ADDED');
    }

    if (b) {
        diffObj.b = {
            name:   b.name,
            path:   b.path,
            offset: b.offset,
            style:  b.style
        };
    } else {
        differences.push('NODE_REMOVED');
    }

    if (differences.length > 0 ) {
        diffObj.differences = differences;
        diff.push(diffObj);
    }

    if (!visualMatching) {
        nodesLengthA = (a && a.nodes && a.nodes.length) || 0;
        nodesLengthB = (b && b.nodes && b.nodes.length) || 0;

        length = Math.max(nodesLengthA, nodesLengthB);

        for(; i < length; i++) {
            compare(a && a.nodes && a.nodes[i], b && b.nodes && b.nodes[i]);
        }
    }

}

function isMatchingStyle(a, b) {
    var filteredAttributes = [ 'top', 'left', 'right', 'bottom', 'width', 'height', 'perspective-origin', 'transform-origin', 'cursor' ];

    filteredAttributes.forEach(function(attribute) {
        delete a.style[attribute];
        delete b.style[attribute];
    });

    return JSON.stringify(a.style) === JSON.stringify(b.style);
}

function isMatchingName(a, b) {
    return (a.name === b.name);
}

function isMatchingOffset(a, b) {
    a = a.offset;
    b = b.offset;
    return (a.width === b.width && a.height === b.height && a.left === b.left && a.top === b.top);
}

function isMatchingSize(a, b) {
    a = a.offset;
    b = b.offset;
    return (Math.abs(a.width - b.width) <= config.sizeDiff && Math.abs(a.height - b.height) <= config.sizeDiff);
}

function sameNodes(a, b) {
    return a && b && isMatchingName(a, b) && isMatchingSize(a, b) && isMatchingStyle(a, b) && isMatchingVisually(a, b);
}

function cleanUpDiff() {
    var i, j,
        length = diff.length,
        leftValue,
        rightValue;

    for (j = 0; j < length; j++) {
        for (i = 0; i < length; i++) {

            leftValue  = diff[j];
            rightValue = diff[i];

            if (leftValue.a && rightValue.b && sameNodes(leftValue.a, rightValue.b)) {
                if (leftValue.differences.indexOf('NODE_ADDED') === -1) { leftValue.differences.push('NODE_ADDED'); }
                if (rightValue.differences.indexOf('NODE_REMOVED') === -1) { rightValue.differences.push('NODE_REMOVED'); }
                diff[j].aHasCopy = i;
                diff[i].bHasCopy = j;
                leftValue.deleteA = true;
                rightValue.deleteB = true;
            }

            if (leftValue.b && rightValue.a && sameNodes(rightValue.a, leftValue.b)) {
                if (leftValue.differences.indexOf('NODE_REMOVED') === -1) { leftValue.differences.push('NODE_REMOVED'); }
                if (rightValue.differences.indexOf('NODE_ADDED') === -1) { rightValue.differences.push('NODE_ADDED'); }
                diff[j].bHasCopy = i;
                diff[i].aHasCopy = j;
                leftValue.deleteB = true;
                rightValue.deleteA = true;
            }
        }

    }

    if (config.cleanup) {
        diff = diff.filter(function(diffItem, i) {

            var pixelsChangedOnly = diffItem.differences.indexOf('NOT_MATCHING_VISUALLY') > -1 &&
                                    diffItem.differences.indexOf('NOT_MATCHING_STYLE') === -1 &&
                                    diffItem.differences.indexOf('NODE_ADDED') === -1 &&
                                    diffItem.differences.indexOf('NODE_REMOVED') === -1;

            if (diffItem.a && (diffItem.deleteA || pixelsChangedOnly)) {
                delete diff[i].a;
                delete diff[i].deleteA;
                delete diff[i].aHasCopy;
            }

            if (diffItem.b && (diffItem.deleteB || pixelsChangedOnly)) {
                delete diff[i].b;
                delete diff[i].deleteB;
                delete diff[i].bHasCopy;
            }

            return diff[i].a || diff[i].b;
        });
    }

}

function compareFingerprints(a, b, cb) {
    var imgUrlA = 'public' + a.screenshot,
        imgUrlB = 'public' + b.screenshot;

    fs.createReadStream(imgUrlA)
        .pipe(new PNG({
            filterType: 4
        }))
        .on('parsed', function() {
            a.png = this;
            processImages(a, b, cb);
        });

    fs.createReadStream(imgUrlB)
        .pipe(new PNG({
            filterType: 4
        }))
        .on('parsed', function() {
            b.png = this;
            processImages(a, b, cb);
        });
}

function processImages(a, b, cb) {
    if (a.png && b.png) {
        imgA = a.png;
        imgB = b.png;
        domA = a.domTree;
        domB = b.domTree;

        compare(domA.nodes[0], domB.nodes[0]);
        cleanUpDiff();
        cb(diff);
    }
}

module.exports = {
    diff: function(a, b, cb) {
        diff = [];
        compareFingerprints(a, b, cb);
    }
};
<commit_msg>Remove child diffs in case of add/remove<commit_after>var fs          = require('fs'),
    PNG         = require('node-png').PNG,
    domA        = {},
    domB        = {},
    imgA        = [],
    imgB        = [],
    debug       = false,
    config      = {
        blur:               !debug,
        movingPixels:       !debug,
        sizeDiff:           debug ? 0 : 1,
        threshold:          debug ? 0 : 15,
        cleanup:            !debug
    },
    diff;

function comparePixels(a, b) {
    var i = 0,
        length = a.length;

    if (a === b) return true;

    for (; i < length; i++) {
        if (Math.abs(b[i] - a[i]) > config.threshold) {
            return false;
        }
    }

    return true;
}

function getPixel(png, x, y) {

    if (config.blur) {
        return getPixelBlur(png, x, y);
    }

    var idx = (y * png.width + x) << 2;

    return [
        png.data[idx],
        png.data[idx + 1],
        png.data[idx + 2]
    ]
}

function getPixelBlur(png, x, y) {
    var width   = png.width,
        idx     = (y * width + x) << 2,
        idxT    = ((y - 1) * width + x) << 2,
        idxL    = (y * width + (x - 1)) << 2,
        idxR    = (y * width + (x + 1)) << 2,
        idxB    = ((y + 1) * width + x) << 2,
        idxTL   = ((y - 1) * width + (x - 1)) << 2,
        idxTR   = ((y - 1) * width + (x + 1)) << 2,
        idxBL   = ((y + 1) * width + (x - 1)) << 2,
        idxBR   = ((y + 1) * width + (x + 1)) << 2;

    return [
        Math.round((
            png.data[idx] + png.data[idxT] + png.data[idxL] + png.data[idxR] + png.data[idxB] +
            png.data[idxTL] + png.data[idxTR] + png.data[idxBL] + png.data[idxBR]
        ) / 9),
        Math.round((
            png.data[idx + 1] + png.data[idxT + 1] + png.data[idxL + 1] + png.data[idxR + 1] + png.data[idxB + 1] +
            png.data[idxTL + 1] + png.data[idxTR + 1] + png.data[idxBL + 1] + png.data[idxBR + 1]
        ) / 9),
        Math.round((
            png.data[idx + 2] + png.data[idxT + 2] + png.data[idxL + 2] + png.data[idxR + 2] + png.data[idxB + 2] +
            png.data[idxTL + 2] + png.data[idxTR + 2] + png.data[idxBL + 2] + png.data[idxBR + 2]
        ) / 9)
    ]
}

function compareSlices(a, b) {
    var imgAWidth   = imgA.width,
        imgAHeight  = imgA.height,
        imgBWidth   = imgB.width,
        imgBHeight  = imgB.height,
        aTop        = Math.max(a.offset.top - 1, 0),
        aLeft       = Math.max(a.offset.left - 1, 0),
        aWidth      = Math.min(a.offset.width + 2, imgAWidth - aLeft),
        aHeight     = Math.min(a.offset.height + 2, imgAHeight - aTop),
        bTop        = Math.max(b.offset.top - 1, 0),
        bLeft       = Math.max(b.offset.left - 1, 0),
        bWidth      = Math.min(b.offset.width + 2, imgBWidth - bLeft),
        bHeight     = Math.min(b.offset.height + 2, imgBHeight - bTop),
        width       = Math.min(aWidth, bWidth) - 1,
        height      = Math.min(aHeight, bHeight) - 1,
        x,
        y,
        srcPixel,
        bLeftX,
        bTopY;

    for (y = 1; y < height; y++) {
        for (x = 1; x < width; x++) {
            srcPixel = getPixel(imgA, aLeft + x, aTop + y);
            bLeftX = bLeft + x;
            bTopY = bTop + y;

            if (config.movingPixels) {
                if (!(comparePixels(srcPixel, getPixel(imgB, bLeftX, bTopY)) ||
                    comparePixels(srcPixel, getPixel(imgB, bLeftX, bTopY - 1)) ||
                    comparePixels(srcPixel, getPixel(imgB, bLeftX, bTopY + 1)) ||
                    comparePixels(srcPixel, getPixel(imgB, bLeftX + 1, bTopY)) ||
                    comparePixels(srcPixel, getPixel(imgB, bLeftX - 1, bTopY)) )) {
                    return false;
                }
            } else {
                if (!(comparePixels(srcPixel, getPixel(imgB, bLeftX, bTopY)))) {
                    return false;
                }
            }
        }
    }

    return true;
}

function isMatchingVisually(a, b) {
    var isVisibleA = a.offset.width > 0 && a.offset.height > 0,
        isVisibleB = b.offset.width > 0 && b.offset.height > 0;

    if (isVisibleA && isVisibleB && isMatchingSize(a, b)) {
        return compareSlices(a, b);
    } else {
        return false;
    }
}

function compare(a, b) {
    var nameMatching,
        offsetMatching,
        visualMatching,
        styleMatching,
        differences = [],
        diffObj = {},
        nodesLengthA,
        nodesLengthB,
        i = 0,
        length;

    if (a && b) {

        nameMatching = isMatchingName(a, b);
        offsetMatching = isMatchingOffset(a, b);
        styleMatching = isMatchingStyle(a, b);

        if (!(nameMatching && offsetMatching && styleMatching)) {

            if (!nameMatching) { differences.push('NOT_MATCHING_NAME'); }

            if (!offsetMatching && a && b) {
                if (a.offset.width > b.offset.width)    { differences.push('OFFSET_WIDTH_LOWER'); }
                if (a.offset.width < b.offset.width)    { differences.push('OFFSET_WIDTH_HIGHER'); }
                if (a.offset.height > b.offset.height ) { differences.push('OFFSET_HEIGHT_LOWER'); }
                if (a.offset.height < b.offset.height ) { differences.push('OFFSET_HEIGHT_HIGHER'); }
                if (a.offset.left > b.offset.left)      { differences.push('OFFSET_X_LOWER'); }
                if (a.offset.left < b.offset.left)      { differences.push('OFFSET_X_HIGHER'); }
                if (a.offset.top > b.offset.top)        { differences.push('OFFSET_Y_LOWER'); }
                if (a.offset.top < b.offset.top)        { differences.push('OFFSET_Y_HIGHER'); }
            }
        }

        if (!styleMatching) {
            differences.push('NOT_MATCHING_STYLE');
        }

        if (styleMatching && isMatchingSize(a, b)) {
            visualMatching = isMatchingVisually(a, b);
        } else {
            visualMatching = false;
        }

        if(!visualMatching) {
            differences.push('NOT_MATCHING_VISUALLY');
        }
    }

    if (a) {
        diffObj.a = {
            name:   a.name,
            path:   a.path,
            offset: a.offset,
            style:  a.style
        };
    } else {
        differences.push('NODE_ADDED');
    }

    if (b) {
        diffObj.b = {
            name:   b.name,
            path:   b.path,
            offset: b.offset,
            style:  b.style
        };
    } else {
        differences.push('NODE_REMOVED');
    }

    if (differences.length > 0 ) {
        diffObj.differences = differences;
        diff.push(diffObj);
    }

    if (!visualMatching) {
        nodesLengthA = (a && a.nodes && a.nodes.length) || 0;
        nodesLengthB = (b && b.nodes && b.nodes.length) || 0;

        length = Math.max(nodesLengthA, nodesLengthB);

        for(; i < length; i++) {
            compare(a && a.nodes && a.nodes[i], b && b.nodes && b.nodes[i]);
        }
    }

}

function isMatchingStyle(a, b) {
    var filteredAttributes = [ 'top', 'left', 'right', 'bottom', 'width', 'height', 'perspective-origin', 'transform-origin', 'cursor' ];

    filteredAttributes.forEach(function(attribute) {
        delete a.style[attribute];
        delete b.style[attribute];
    });

    return JSON.stringify(a.style) === JSON.stringify(b.style);
}

function isMatchingName(a, b) {
    return (a.name === b.name);
}

function isMatchingOffset(a, b) {
    a = a.offset;
    b = b.offset;
    return (a.width === b.width && a.height === b.height && a.left === b.left && a.top === b.top);
}

function isMatchingSize(a, b) {
    a = a.offset;
    b = b.offset;
    return (Math.abs(a.width - b.width) <= config.sizeDiff && Math.abs(a.height - b.height) <= config.sizeDiff);
}

function sameNodes(a, b) {
    return a && b && isMatchingName(a, b) && isMatchingSize(a, b) && isMatchingStyle(a, b) && isMatchingVisually(a, b);
}

function cleanUpDiff() {
    var i, j,
        length = diff.length,
        leftValue,
        rightValue;

    for (j = 0; j < length; j++) {
        for (i = 0; i < length; i++) {

            leftValue  = diff[j];
            rightValue = diff[i];

            if (leftValue.a && rightValue.b && sameNodes(leftValue.a, rightValue.b)) {
                if (leftValue.differences.indexOf('NODE_ADDED') === -1) { leftValue.differences.push('NODE_ADDED'); }
                if (rightValue.differences.indexOf('NODE_REMOVED') === -1) { rightValue.differences.push('NODE_REMOVED'); }
                diff[j].aHasCopy = i;
                diff[i].bHasCopy = j;
                leftValue.deleteA = true;
                rightValue.deleteB = true;
            }

            if (leftValue.b && rightValue.a && sameNodes(rightValue.a, leftValue.b)) {
                if (leftValue.differences.indexOf('NODE_REMOVED') === -1) { leftValue.differences.push('NODE_REMOVED'); }
                if (rightValue.differences.indexOf('NODE_ADDED') === -1) { rightValue.differences.push('NODE_ADDED'); }
                diff[j].bHasCopy = i;
                diff[i].aHasCopy = j;
                leftValue.deleteB = true;
                rightValue.deleteA = true;
            }
        }

    }

    if (config.cleanup) {
        diff = diff.filter(function(diffItem, i) {

            var pixelsChangedOnly = diffItem.differences.indexOf('NOT_MATCHING_VISUALLY') > -1 &&
                                    diffItem.differences.indexOf('NOT_MATCHING_STYLE') === -1 &&
                                    diffItem.differences.indexOf('NODE_ADDED') === -1 &&
                                    diffItem.differences.indexOf('NODE_REMOVED') === -1;

            diff.forEach(function(item, index) {
                if (diffItem.a && item.a && diffItem.differences.indexOf('NODE_REMOVED') > -1 && diffItem.a.path !== item.a.path && diffItem.a.path.indexOf(item.a.path) > -1) {
                    diffItem.deleteA = true;
                }
                if (diffItem.b && item.b && diffItem.differences.indexOf('NODE_ADDED') > -1 && diffItem.b.path !== item.b.path && diffItem.b.path.indexOf(item.b.path) > -1) {
                    diffItem.deleteB = true;
                }
            });

            if (diffItem.a && (diffItem.deleteA || pixelsChangedOnly)) {
                delete diff[i].a;
                delete diff[i].deleteA;
                delete diff[i].aHasCopy;
            }

            if (diffItem.b && (diffItem.deleteB || pixelsChangedOnly)) {
                delete diff[i].b;
                delete diff[i].deleteB;
                delete diff[i].bHasCopy;
            }

            return diff[i].a || diff[i].b;
        });

    }

}

function compareFingerprints(a, b, cb) {
    var imgUrlA = 'public' + a.screenshot,
        imgUrlB = 'public' + b.screenshot;

    fs.createReadStream(imgUrlA)
        .pipe(new PNG({
            filterType: 4
        }))
        .on('parsed', function() {
            a.png = this;
            processImages(a, b, cb);
        });

    fs.createReadStream(imgUrlB)
        .pipe(new PNG({
            filterType: 4
        }))
        .on('parsed', function() {
            b.png = this;
            processImages(a, b, cb);
        });
}

function processImages(a, b, cb) {
    if (a.png && b.png) {
        imgA = a.png;
        imgB = b.png;
        domA = a.domTree;
        domB = b.domTree;

        compare(domA.nodes[0], domB.nodes[0]);
        cleanUpDiff();
        cb(diff);
    }
}

module.exports = {
    diff: function(a, b, cb) {
        diff = [];
        compareFingerprints(a, b, cb);
    }
};
<|endoftext|>


<commit_before>const _ = require('lodash');
const Promise = require('bluebird');
const debug = require('debug')('lib:parse');
const moment = require('moment');
const nconf = require('nconf');
const xpath = require('xpath');
const xmldom = require('xmldom');
const JSDOM = require('jsdom').JSDOM;

const mongo = require('./mongo');
const sequence = require('../parsers/components/utils/sequence');

nconf.argv().env().file({ file: "config/collector.json" });

function initialize(impression, repeat) {

    if(_.isUndefined(impression.id))
        throw new Error("impression missing");

    return mongo
        .readOne(nconf.get('schema').metadata, { id: impression.id })
        .then(function(i) {

            if( _.get(i, 'id') === impression.id && !repeat) {
                debug("metadata [%s] already exists: skipping", i.id);
                return null;
            }

            if( _.get(i, 'id') === impression.id && repeat)
                debug("metadata [%s] exists, but repeat is requested", i.id);

            const xmlerr = { warning: 0, error: 0, fatal: 0 };
            var domOptions = {
                  errorHandler:{
                      warning: function() { xmlerr.warning += 1; },
                      error: function() { xmlerr.error += 1; },
                      fatalError: function() { xmlerr.fatal += 1;  },
                  }
            };

            /* this is the envelope with get appended the metadata from the various parsers */
            return {
                impression,
                xmlerr,
                xpath,
                jsdom: new JSDOM(impression.html).window.document,
                dom: new xmldom.DOMParser(domOptions).parseFromString(impression.html),
            };
        });
}

function save(metadata) {
    if(!_.size(metadata))
        return [];

    /* this update three columns (with different data retention politcy)
     * the metadata.id === htmls.id */
    const stats = _.map(metadata, function(e) {
        /* stats are generated by components/utils/stats.js,
         * all the stats are saved, even if duplicated */
        var r = e.stats;
        r.when = new Date();
        r.id = e.id;
        return r;
    });
    const onlyMetadata = _.map(metadata, function(e) {
        return _.omit(e, ['stats', 'errors', 'summary']);
    });
    const errors = _.reduce(metadata, function(memo, e) {
        if(!_.size(e.errors))
            return memo;
        var r = {
            errors: e.errors,
            when:  new Date(),
            id: e.id,
            timelineId: e.timelineId
        }
        memo.push(r);
        return memo;
    }, []);

    const summary = _.map(metadata, function(e) {
        /* this do not take the ID because is an information we can eventually share
         * to the outside */
        return e.summary;
    });

    let chain = [
        mongo.writeMany(nconf.get('schema').parserstats, stats),
        mongo.writeMany(nconf.get('schema').metadata, onlyMetadata),
        mongo.writeMany(nconf.get('schema').summary, summary)
    ];
    if(_.size(errors)) {
        chain.push(
            mongo.writeMany(nconf.get('schema').errors, errors)
        );
    }
    debug("Received %d (stats, metadata) and %d errors",
        _.size(metadata), _.size(errors)
    );
    return Promise
        .all(chain)
        .return({
            metadata: _.size(metadata),
            errors: _.size(errors)
        });
}

function mark(metadata) {
    /* this is the second function called, when the html is marked as processed,
     * probably the condition `false` should be considered worthy */

    return Promise.map(metadata, function(e) {
        return mongo
            .readOne(nconf.get('schema').htmls, { id: e.id })
            .then(function(existing) {
                existing.processed = true;
                return mongo
                    .updateOne(nconf.get('schema').htmls, { _id: existing._id }, existing);
            });
    }, { concurrency: 5});
}

function parseHTML(htmlfilter, repeat) {
    return mongo
        .read(nconf.get('schema').htmls, htmlfilter)
        .map(function(html) {
            return mongo
                .readOne(nconf.get('schema').impressions, { id: html.impressionId })
                .then(function(impression) {
                    _.unset(impression, 'id');
                    _.unset(impression, 'htmlId');
                    return _.merge(html, impression);
                });
        }, { concurrency: 10 })
        .then(_.compact)
        .then(function(impressions) {
            return _.orderBy(impressions, { impressionOrder: -1 });
        })
        .tap(function(impressions) {
            if(_.size(impressions)) {
                const firstT = moment(_.first(impressions).impressionTime).format("DD/MMM HH:mm");
                const lastT = moment(_.last(impressions).impressionTime).format("DD/MMM HH:mm");
                debug("Found %d impressions associated: %d [%s] - %d [%s] (%s)",
                    _.size(impressions),
                    _.first(impressions).impressionOrder, firstT,
                    _.last(impressions).impressionOrder, lastT,
                    moment.duration(
                        moment(_.last(impressions).impressionTime) -
                        moment(_.first(impressions).impressionTime ))
                    .humanize()
                );
            }
        })
        .map(function(e) {
            return initialize(e, repeat);
        }, { concurrency: 10})
        .then(_.compact)
        .map(sequence)
        /* this is the function processing the parsers
         * it is call of every .id which should be analyzed */
        .catch(function(error) {
            debug("[E] Unmanaged error in parser sequence: %s", error.message);
            console.log(error.stack);
            return null;
        })
        .map(function(results) {
            const removef = ['dom', 'jsdom', 'xpath', 'xmlerr', 'impression'];
            const impressionFields = ['id', 'timelineId', 'userId', 'impressionOrder', 'impressionTime'];

            let r = _.omit(results, removef);
            _.extend(r, _.pick(results.impression, impressionFields));

            /* log some stats about the success ratio, or compute the
             * summary which would be saved later by 'save' */
            return r;
        })
        .tap(function(metadata) {
            debug("⁂  completed and filtered %d metadata", _.size(metadata));
            if(!_.isUndefined(nconf.get('verbose')))
                debug("⁂  %s", JSON.stringify(metadata, undefined, 2));
        })
        .tap(mark)
        .then(save)
        .catch(function(error) {
            debug("[error after parsing] %s", error.message);
            console.log(error.stack);
            return null;
        });
}

module.exports = {
    initialize: initialize,
    save: save,
    mark: mark,
    parseHTML: parseHTML,
};
<commit_msg>forced concurrency 1 in parserv<commit_after>const _ = require('lodash');
const Promise = require('bluebird');
const debug = require('debug')('lib:parse');
const moment = require('moment');
const nconf = require('nconf');
const xpath = require('xpath');
const xmldom = require('xmldom');
const JSDOM = require('jsdom').JSDOM;

const mongo = require('./mongo');
const sequence = require('../parsers/components/utils/sequence');

nconf.argv().env().file({ file: "config/collector.json" });

function initialize(impression, repeat) {

    if(_.isUndefined(impression.id))
        throw new Error("impression missing");

    return mongo
        .readOne(nconf.get('schema').metadata, { id: impression.id })
        .then(function(i) {

            if( _.get(i, 'id') === impression.id && !repeat) {
                debug("metadata [%s] already exists: skipping", i.id);
                return null;
            }

            if( _.get(i, 'id') === impression.id && repeat)
                debug("metadata [%s] exists, but repeat is requested", i.id);

            const xmlerr = { warning: 0, error: 0, fatal: 0 };
            var domOptions = {
                  errorHandler:{
                      warning: function() { xmlerr.warning += 1; },
                      error: function() { xmlerr.error += 1; },
                      fatalError: function() { xmlerr.fatal += 1;  },
                  }
            };

            /* this is the envelope with get appended the metadata from the various parsers */
            return {
                impression,
                xmlerr,
                xpath,
                jsdom: new JSDOM(impression.html).window.document,
                dom: new xmldom.DOMParser(domOptions).parseFromString(impression.html),
            };
        });
}

function save(metadata) {
    if(!_.size(metadata))
        return [];

    /* this update three columns (with different data retention politcy)
     * the metadata.id === htmls.id */
    const stats = _.map(metadata, function(e) {
        /* stats are generated by components/utils/stats.js,
         * all the stats are saved, even if duplicated */
        var r = e.stats;
        r.when = new Date();
        r.id = e.id;
        return r;
    });
    const onlyMetadata = _.map(metadata, function(e) {
        return _.omit(e, ['stats', 'errors', 'summary']);
    });
    const errors = _.reduce(metadata, function(memo, e) {
        if(!_.size(e.errors))
            return memo;
        var r = {
            errors: e.errors,
            when:  new Date(),
            id: e.id,
            timelineId: e.timelineId
        }
        memo.push(r);
        return memo;
    }, []);

    const summary = _.map(metadata, function(e) {
        /* this do not take the ID because is an information we can eventually share
         * to the outside */
        return e.summary;
    });

    let chain = [
        mongo.writeMany(nconf.get('schema').parserstats, stats),
        mongo.writeMany(nconf.get('schema').metadata, onlyMetadata),
        mongo.writeMany(nconf.get('schema').summary, summary)
    ];
    if(_.size(errors)) {
        chain.push(
            mongo.writeMany(nconf.get('schema').errors, errors)
        );
    }
    debug("Received %d (stats, metadata) and %d errors",
        _.size(metadata), _.size(errors)
    );
    return Promise
        .all(chain)
        .return({
            metadata: _.size(metadata),
            errors: _.size(errors)
        });
}

function mark(metadata) {
    /* this is the second function called, when the html is marked as processed,
     * probably the condition `false` should be considered worthy */

    return Promise.map(metadata, function(e) {
        return mongo
            .readOne(nconf.get('schema').htmls, { id: e.id })
            .then(function(existing) {
                existing.processed = true;
                return mongo
                    .updateOne(nconf.get('schema').htmls, { _id: existing._id }, existing);
            });
    }, { concurrency: 1});
}

function parseHTML(htmlfilter, repeat) {
    return mongo
        .read(nconf.get('schema').htmls, htmlfilter)
        .map(function(html) {
            return mongo
                .readOne(nconf.get('schema').impressions, { id: html.impressionId })
                .then(function(impression) {
                    _.unset(impression, 'id');
                    _.unset(impression, 'htmlId');
                    return _.merge(html, impression);
                });
        }, { concurrency: 1 })
        .then(_.compact)
        .then(function(impressions) {
            return _.orderBy(impressions, { impressionOrder: -1 });
        })
        .tap(function(impressions) {
            if(_.size(impressions)) {
                const firstT = moment(_.first(impressions).impressionTime).format("DD/MMM HH:mm");
                const lastT = moment(_.last(impressions).impressionTime).format("DD/MMM HH:mm");
                debug("Found %d impressions associated: %d [%s] - %d [%s] (%s)",
                    _.size(impressions),
                    _.first(impressions).impressionOrder, firstT,
                    _.last(impressions).impressionOrder, lastT,
                    moment.duration(
                        moment(_.last(impressions).impressionTime) -
                        moment(_.first(impressions).impressionTime ))
                    .humanize()
                );
            }
        })
        .map(function(e) {
            return initialize(e, repeat);
        }, { concurrency: 1 })
        .then(_.compact)
        .map(sequence, { concurrency: 1 })
        /* this is the function processing the parsers
         * it is call of every .id which should be analyzed */
        .catch(function(error) {
            debug("[E] Unmanaged error in parser sequence: %s", error.message);
            console.log(error.stack);
            return null;
        })
        .map(function(results) {
            const removef = ['dom', 'jsdom', 'xpath', 'xmlerr', 'impression'];
            const impressionFields = ['id', 'timelineId', 'userId', 'impressionOrder', 'impressionTime'];

            let r = _.omit(results, removef);
            _.extend(r, _.pick(results.impression, impressionFields));

            /* log some stats about the success ratio, or compute the
             * summary which would be saved later by 'save' */
            return r;
        }, { concurrency: 1})
        .tap(function(metadata) {
            debug("⁂  completed and filtered %d metadata", _.size(metadata));
            if(!_.isUndefined(nconf.get('verbose')))
                debug("⁂  %s", JSON.stringify(metadata, undefined, 2));
        })
        .tap(mark)
        .then(save)
        .catch(function(error) {
            debug("[error after parsing] %s", error.message);
            console.log(error.stack);
            return null;
        });
}

module.exports = {
    initialize: initialize,
    save: save,
    mark: mark,
    parseHTML: parseHTML,
};
<|endoftext|>


<commit_before>Comments = (function () {
  var timeTick = new Tracker.Dependency(),
    collection = new Mongo.Collection('comments'),
    noOptOptions = {
      validate: false,
      filter: false,
      getAutoValues: false,
      removeEmptyStrings: false
    },
    ReplySchema, CommentSchema;

  /*
   * Helper Functions
   */

  function transformUser(userId) {
    var user = Meteor.users.findOne(userId),
      displayName;

    if (user) {
      if (user.emails && user.emails[0]) {
        displayName = user.emails[0].address;
      }

      if (user.username) {
        displayName = user.username;
      }

      return { displayName: displayName };
    }
  }

  function transformReplies(scope, position) {
    if (!position) {
      position = [];
    }

    return _.map(scope.replies, function (reply, index) {
      position.push(index);

      reply.position = position;
      reply.documentId = scope._id;

      reply.user = scope.user.bind(reply);
      reply.likesCount = scope.likesCount.bind(reply);
      reply.createdAgo = scope.createdAgo.bind(reply);

      // clone position
      reply.position = position.slice(0);

      if (reply.replies) {
        // recursive!
        reply.enhancedReplies = _.bind(transformReplies, null, _.extend(_.clone(scope), { replies: reply.replies }), position)();
      }

      position.pop();

      return reply;
    });
  }

  function modifyNestedReplies(nestedArray, position, callback) {
    var currentPos = position.shift();

    if (nestedArray[currentPos]) {
      if (position.length && nestedArray[currentPos] && nestedArray[currentPos].replies) {
        modifyNestedReplies(nestedArray[currentPos].replies, position, callback);
      } else {
        callback(nestedArray, currentPos);
      }
    }
  }

  function getUserIdsByComment(comment) {
    var ids = [];

    ids.push(comment.userId);

    if (comment.replies) {
      _.each(comment.replies, function (reply) {
        ids = _.union(ids, getUserIdsByComment(reply));
      });
    }

    return ids;
  }

  function getImageFromContent(content) {
    var urls;

    if (content) {
      urls = content.match(/(\S+\.[^/\s]+(\/\S+|\/|))(.jpg|.png|.gif)/g) ;

      if (urls && urls[0]) {
        return urls[0];
      }
    }

    return '';
  }

  /*
   * Schema Definitions
   */

  ReplySchema = new SimpleSchema({
    replyId: {
      type: String
    },
    userId: {
      type: String
    },
    image: {
      type: String,
      optional: true,
      autoValue: function () {
        return getImageFromContent(this.siblingField('content').value);
      }
    },
    content: {
      type: String,
      min: 1,
      max: 10000
    },
    replies: {
      type: [Object],
      autoValue: function (doc) {
        if (this.isInsert) {
          return [];
        }
      },
      optional: true
    },
    likes: {
      type: [String],
      autoValue: function() {
        if (this.isInsert) {
          return [];
        }
      },
      optional: true
    },
    createdAt: {
      type: Date,
      autoValue: function() {
        if (this.isInsert) {
          return new Date;
        } else if (this.isUpsert) {
          return {$setOnInsert: new Date};
        } else {
          this.unset();
        }
      }
    },
    lastUpdatedAt: {
      type: Date,
      autoValue: function() {
        if (this.isUpdate) {
          return new Date();
        }
      },
      denyInsert: true,
      optional: true
    }
  });

  CommentSchema = new SimpleSchema({
    userId: {
      type: String
    },
    referenceId: {
      type: String
    },
    image: {
      type: String,
      optional: true,
      autoValue: function () {
        return getImageFromContent(this.siblingField('content').value);
      }
    },
    content: {
      type: String,
      min: 1,
      max: 10000
    },
    replies: {
      type: [Object],
      autoValue: function () {
        if (this.isInsert) {
          return [];
        }
      },
      optional: true
    },
    likes: {
      type: [String],
      autoValue: function() {
        if (this.isInsert) {
          return [];
        }
      },
      optional: true
    },
    createdAt: {
      type: Date,
      autoValue: function() {
        if (this.isInsert) {
          return new Date;
        } else if (this.isUpsert) {
          return {$setOnInsert: new Date};
        } else {
          this.unset();
        }
      }
    },
    lastUpdatedAt: {
      type: Date,
      autoValue: function() {
        if (this.isUpdate) {
          return new Date();
        }
      },
      denyInsert: true,
      optional: true
    }
  });

  /*
   * Model Configuration
   */

  // Reactive moment changes
  Meteor.setInterval(function () {
    timeTick.changed();
  }, 1000);

  function fromNowReactive(mmt) {
    timeTick.depend();
    return mmt.fromNow();
  }

  collection.attachSchema(CommentSchema);

  // Is handled with Meteor.methods
  collection.allow({
    insert: function () { return false; },
    update: function () { return false; },
    remove: function () { return false; }
  });

  collection.helpers({
    likesCount: function () {
      if (this.likes && this.likes.length) {
        return this.likes.length;
      }

      return 0;
    },
    user: function () {
      return transformUser(this.userId);
    },
    createdAgo: function () {
      return fromNowReactive(moment(this.createdAt));
    },
    enhancedReplies: function (position) {
      return transformReplies(this);
    }
  });

  /*
   * Meteor Methods
   */

  Meteor.methods({
    'comments/add': function (referenceId, content) {
      check(referenceId, String);
      check(content, String);

      content = content.trim();

      if (!this.userId || !content) {
        return;
      }

      collection.insert(
          { referenceId: referenceId, content: content, userId: this.userId, createdAt: (new Date()), likes: [], replies: [] }
      );
    },
    'comments/edit': function (documentId, newContent) {
      check(documentId, String);
      check(newContent, String);

      newContent = newContent.trim();

      if (!this.userId || !newContent) {
        return;
      }

      collection.update(
        { _id: documentId, userId: this.userId },
        { $set: { content: newContent, likes: [], image: getImageFromContent(newContent) } }
      );
    },
    'comments/remove': function (documentId) {
      check(documentId, String);
      collection.remove({ _id: documentId, userId: this.userId });
    },
    'comments/like': function (documentId) {
      check (documentId, String);
      check(this.userId, String);

      if (!this.userId) {
        return;
      }

      if (collection.findOne({ _id: documentId, likes: { $in: [this.userId] } })) {
        collection.update({ _id: documentId }, { $pull: { likes: this.userId } }, noOptOptions)
      } else {
        collection.update({ _id: documentId }, { $push: { likes: this.userId } }, noOptOptions)
      }
    },
    'comments/reply/add': function (documentId, docScope, content) {
      check(documentId, String);
      check(docScope, Object);
      check(content, String);

      var doc = collection.findOne({ _id: documentId }),
          reply;

      content = content.trim();

      if (!doc || !this.userId || !content) {
        return false;
      }

      reply = {
        replyId: Random.id(),
        content: content,
        userId: this.userId,
        createdAt: (new Date()),
        replies: [], likes: [],
        lastUpdatedAt: (new Date())
      };

      check(reply, ReplySchema);

      if (docScope._id) {
        // highest level reply
        doc.replies.unshift(reply);
      } else if (docScope.position) {
        // nested reply
        modifyNestedReplies(doc.replies, docScope.position, function (replies, index) {
          replies[index].replies.unshift(reply);
        });
      }

      collection.update({ _id: documentId }, { $set: { replies: doc.replies } }, noOptOptions);
    },
    'comments/reply/edit': function (documentId, docScope, newContent) {
      check(documentId, String);
      check(docScope, Object);
      check(newContent, String);

      var doc = collection.findOne(documentId),
          userId = this.userId;

      newContent = newContent.trim();

      if (!userId || !newContent) {
        return;
      }

      modifyNestedReplies(doc.replies, docScope.position, function (replies, index) {
        if (replies[index].userId === userId) {
          replies[index].content = newContent;
          replies[index].likes = [];
          replies[index].image = getImageFromContent(newContent);
        }
      });

      collection.update({ _id: documentId }, { $set: { replies: doc.replies } }, noOptOptions);
    },
    'comments/reply/like': function (documentId, docScope) {
      check(documentId, String);
      check(docScope, Object);

      var doc = collection.findOne({ _id: documentId }),
          userId = this.userId;

      if (!userId) {
        return;
      }

      modifyNestedReplies(doc.replies, docScope.position, function (replies, index) {
        if (replies[index].likes.indexOf(userId) > -1) {
          replies[index].likes.splice(replies[index].likes.indexOf(userId), 1);
        } else {
          replies[index].likes.push(userId);
        }
      });

      collection.update({ _id: documentId }, { $set: { replies: doc.replies }  }, noOptOptions);
    },
    'comments/reply/remove': function (documentId, docScope) {
      check(documentId, String);
      check(docScope, Object);

      var doc = collection.findOne({ _id: documentId }),
          userId = this.userId;

      if (!userId) {
        return;
      }

      modifyNestedReplies(doc.replies, docScope.position, function (replies, index) {
        if (replies[index].userId === userId) {
          replies.splice(index, 1);
        }
      });

      collection.update({ _id: documentId }, { $set: { replies: doc.replies }  }, noOptOptions);
    },
    'comments/count': function (referenceId) {
      check(referenceId, String);
      return collection.find({ referenceId: referenceId }).count();
    }
  });

  if (Meteor.isServer) {
    Meteor.publishComposite('comments/reference', function (id, limit) {
      check(id, String);
      check(limit, Number);

      return {
        find: function () {
          return collection.find({ referenceId: id }, { limit: limit, sort: { createdAt: -1 } });
        },
        children: [{
          find: function (comment) {
            var userIds = getUserIdsByComment(comment);

            return Meteor.users.find({ _id: { $in: userIds } }, { fields: { profile: 1, emails: 1, username: 1 } });
          }
        }]
      };
    })
  }

  /*
   * Public API
   */

  return {
    get: function (id) {
      return collection.find({ referenceId: id }, { sort: { createdAt: -1 } });
    },
    getOne: function (id) {
      return collection.findOne({ _id: id });
    },
    getAll: function () {
      return collection.find({}, { sort: { createdAt: -1 } });
    },
    add: function (referenceId, content) {
      Meteor.call('comments/add', referenceId, content);
    },
    edit: function (documentId, newContent) {
      Meteor.call('comments/edit', documentId, newContent);
    },
    remove: function (documentId) {
      Meteor.call('comments/remove', documentId);
    },
    like: function (documentId) {
      Meteor.call('comments/like', documentId);
    },
    reply: function (documentId, docScope, content) {
      Meteor.call('comments/reply/add', documentId, docScope, content);
    },
    editReply: function (documentId, docScope, content) {
      Meteor.call('comments/reply/edit', documentId, docScope, content);
    },
    removeReply: function (documentId, docScope) {
      Meteor.call('comments/reply/remove', documentId, docScope);
    },
    likeReply: function (documentId, docScope) {
      Meteor.call('comments/reply/like', documentId, docScope);
    },
    session: {
      set: function (key, val) {
        return Session.set('commentsUi_' + key, val);
      },
      get: function (key) {
        return Session.get('commentsUi_' + key);
      },
      equals: function (key, val) {
        return Session.equals('commentsUi_' + key, val);
      }
    },
    changeSchema: function (cb) {
      var currentSchema = collection.simpleSchema().schema(),
        callbackResult = cb(currentSchema),
        newSchema;

      newSchema = callbackResult ? callbackResult : currentSchema;
      !!newSchema && collection.attachSchema(newSchema, { replace: true });
    },
    _collection: collection
  };
})();
<commit_msg>Set default locale to en<commit_after>Comments = (function () {
  var timeTick = new Tracker.Dependency(),
    collection = new Mongo.Collection('comments'),
    noOptOptions = {
      validate: false,
      filter: false,
      getAutoValues: false,
      removeEmptyStrings: false
    },
    ReplySchema, CommentSchema;

  moment.locale('en');

  /*
   * Helper Functions
   */

  function transformUser(userId) {
    var user = Meteor.users.findOne(userId),
      displayName;

    if (user) {
      if (user.emails && user.emails[0]) {
        displayName = user.emails[0].address;
      }

      if (user.username) {
        displayName = user.username;
      }

      return { displayName: displayName };
    }
  }

  function transformReplies(scope, position) {
    if (!position) {
      position = [];
    }

    return _.map(scope.replies, function (reply, index) {
      position.push(index);

      reply.position = position;
      reply.documentId = scope._id;

      reply.user = scope.user.bind(reply);
      reply.likesCount = scope.likesCount.bind(reply);
      reply.createdAgo = scope.createdAgo.bind(reply);

      // clone position
      reply.position = position.slice(0);

      if (reply.replies) {
        // recursive!
        reply.enhancedReplies = _.bind(transformReplies, null, _.extend(_.clone(scope), { replies: reply.replies }), position)();
      }

      position.pop();

      return reply;
    });
  }

  function modifyNestedReplies(nestedArray, position, callback) {
    var currentPos = position.shift();

    if (nestedArray[currentPos]) {
      if (position.length && nestedArray[currentPos] && nestedArray[currentPos].replies) {
        modifyNestedReplies(nestedArray[currentPos].replies, position, callback);
      } else {
        callback(nestedArray, currentPos);
      }
    }
  }

  function getUserIdsByComment(comment) {
    var ids = [];

    ids.push(comment.userId);

    if (comment.replies) {
      _.each(comment.replies, function (reply) {
        ids = _.union(ids, getUserIdsByComment(reply));
      });
    }

    return ids;
  }

  function getImageFromContent(content) {
    var urls;

    if (content) {
      urls = content.match(/(\S+\.[^/\s]+(\/\S+|\/|))(.jpg|.png|.gif)/g) ;

      if (urls && urls[0]) {
        return urls[0];
      }
    }

    return '';
  }

  /*
   * Schema Definitions
   */

  ReplySchema = new SimpleSchema({
    replyId: {
      type: String
    },
    userId: {
      type: String
    },
    image: {
      type: String,
      optional: true,
      autoValue: function () {
        return getImageFromContent(this.siblingField('content').value);
      }
    },
    content: {
      type: String,
      min: 1,
      max: 10000
    },
    replies: {
      type: [Object],
      autoValue: function (doc) {
        if (this.isInsert) {
          return [];
        }
      },
      optional: true
    },
    likes: {
      type: [String],
      autoValue: function() {
        if (this.isInsert) {
          return [];
        }
      },
      optional: true
    },
    createdAt: {
      type: Date,
      autoValue: function() {
        if (this.isInsert) {
          return new Date;
        } else if (this.isUpsert) {
          return {$setOnInsert: new Date};
        } else {
          this.unset();
        }
      }
    },
    lastUpdatedAt: {
      type: Date,
      autoValue: function() {
        if (this.isUpdate) {
          return new Date();
        }
      },
      denyInsert: true,
      optional: true
    }
  });

  CommentSchema = new SimpleSchema({
    userId: {
      type: String
    },
    referenceId: {
      type: String
    },
    image: {
      type: String,
      optional: true,
      autoValue: function () {
        return getImageFromContent(this.siblingField('content').value);
      }
    },
    content: {
      type: String,
      min: 1,
      max: 10000
    },
    replies: {
      type: [Object],
      autoValue: function () {
        if (this.isInsert) {
          return [];
        }
      },
      optional: true
    },
    likes: {
      type: [String],
      autoValue: function() {
        if (this.isInsert) {
          return [];
        }
      },
      optional: true
    },
    createdAt: {
      type: Date,
      autoValue: function() {
        if (this.isInsert) {
          return new Date;
        } else if (this.isUpsert) {
          return {$setOnInsert: new Date};
        } else {
          this.unset();
        }
      }
    },
    lastUpdatedAt: {
      type: Date,
      autoValue: function() {
        if (this.isUpdate) {
          return new Date();
        }
      },
      denyInsert: true,
      optional: true
    }
  });

  /*
   * Model Configuration
   */

  // Reactive moment changes
  Meteor.setInterval(function () {
    timeTick.changed();
  }, 1000);

  function fromNowReactive(mmt) {
    timeTick.depend();
    return mmt.fromNow();
  }

  collection.attachSchema(CommentSchema);

  // Is handled with Meteor.methods
  collection.allow({
    insert: function () { return false; },
    update: function () { return false; },
    remove: function () { return false; }
  });

  collection.helpers({
    likesCount: function () {
      if (this.likes && this.likes.length) {
        return this.likes.length;
      }

      return 0;
    },
    user: function () {
      return transformUser(this.userId);
    },
    createdAgo: function () {
      return fromNowReactive(moment(this.createdAt));
    },
    enhancedReplies: function (position) {
      return transformReplies(this);
    }
  });

  /*
   * Meteor Methods
   */

  Meteor.methods({
    'comments/add': function (referenceId, content) {
      check(referenceId, String);
      check(content, String);

      content = content.trim();

      if (!this.userId || !content) {
        return;
      }

      collection.insert(
          { referenceId: referenceId, content: content, userId: this.userId, createdAt: (new Date()), likes: [], replies: [] }
      );
    },
    'comments/edit': function (documentId, newContent) {
      check(documentId, String);
      check(newContent, String);

      newContent = newContent.trim();

      if (!this.userId || !newContent) {
        return;
      }

      collection.update(
        { _id: documentId, userId: this.userId },
        { $set: { content: newContent, likes: [], image: getImageFromContent(newContent) } }
      );
    },
    'comments/remove': function (documentId) {
      check(documentId, String);
      collection.remove({ _id: documentId, userId: this.userId });
    },
    'comments/like': function (documentId) {
      check (documentId, String);
      check(this.userId, String);

      if (!this.userId) {
        return;
      }

      if (collection.findOne({ _id: documentId, likes: { $in: [this.userId] } })) {
        collection.update({ _id: documentId }, { $pull: { likes: this.userId } }, noOptOptions)
      } else {
        collection.update({ _id: documentId }, { $push: { likes: this.userId } }, noOptOptions)
      }
    },
    'comments/reply/add': function (documentId, docScope, content) {
      check(documentId, String);
      check(docScope, Object);
      check(content, String);

      var doc = collection.findOne({ _id: documentId }),
          reply;

      content = content.trim();

      if (!doc || !this.userId || !content) {
        return false;
      }

      reply = {
        replyId: Random.id(),
        content: content,
        userId: this.userId,
        createdAt: (new Date()),
        replies: [], likes: [],
        lastUpdatedAt: (new Date())
      };

      check(reply, ReplySchema);

      if (docScope._id) {
        // highest level reply
        doc.replies.unshift(reply);
      } else if (docScope.position) {
        // nested reply
        modifyNestedReplies(doc.replies, docScope.position, function (replies, index) {
          replies[index].replies.unshift(reply);
        });
      }

      collection.update({ _id: documentId }, { $set: { replies: doc.replies } }, noOptOptions);
    },
    'comments/reply/edit': function (documentId, docScope, newContent) {
      check(documentId, String);
      check(docScope, Object);
      check(newContent, String);

      var doc = collection.findOne(documentId),
          userId = this.userId;

      newContent = newContent.trim();

      if (!userId || !newContent) {
        return;
      }

      modifyNestedReplies(doc.replies, docScope.position, function (replies, index) {
        if (replies[index].userId === userId) {
          replies[index].content = newContent;
          replies[index].likes = [];
          replies[index].image = getImageFromContent(newContent);
        }
      });

      collection.update({ _id: documentId }, { $set: { replies: doc.replies } }, noOptOptions);
    },
    'comments/reply/like': function (documentId, docScope) {
      check(documentId, String);
      check(docScope, Object);

      var doc = collection.findOne({ _id: documentId }),
          userId = this.userId;

      if (!userId) {
        return;
      }

      modifyNestedReplies(doc.replies, docScope.position, function (replies, index) {
        if (replies[index].likes.indexOf(userId) > -1) {
          replies[index].likes.splice(replies[index].likes.indexOf(userId), 1);
        } else {
          replies[index].likes.push(userId);
        }
      });

      collection.update({ _id: documentId }, { $set: { replies: doc.replies }  }, noOptOptions);
    },
    'comments/reply/remove': function (documentId, docScope) {
      check(documentId, String);
      check(docScope, Object);

      var doc = collection.findOne({ _id: documentId }),
          userId = this.userId;

      if (!userId) {
        return;
      }

      modifyNestedReplies(doc.replies, docScope.position, function (replies, index) {
        if (replies[index].userId === userId) {
          replies.splice(index, 1);
        }
      });

      collection.update({ _id: documentId }, { $set: { replies: doc.replies }  }, noOptOptions);
    },
    'comments/count': function (referenceId) {
      check(referenceId, String);
      return collection.find({ referenceId: referenceId }).count();
    }
  });

  if (Meteor.isServer) {
    Meteor.publishComposite('comments/reference', function (id, limit) {
      check(id, String);
      check(limit, Number);

      return {
        find: function () {
          return collection.find({ referenceId: id }, { limit: limit, sort: { createdAt: -1 } });
        },
        children: [{
          find: function (comment) {
            var userIds = getUserIdsByComment(comment);

            return Meteor.users.find({ _id: { $in: userIds } }, { fields: { profile: 1, emails: 1, username: 1 } });
          }
        }]
      };
    })
  }

  /*
   * Public API
   */

  return {
    get: function (id) {
      return collection.find({ referenceId: id }, { sort: { createdAt: -1 } });
    },
    getOne: function (id) {
      return collection.findOne({ _id: id });
    },
    getAll: function () {
      return collection.find({}, { sort: { createdAt: -1 } });
    },
    add: function (referenceId, content) {
      Meteor.call('comments/add', referenceId, content);
    },
    edit: function (documentId, newContent) {
      Meteor.call('comments/edit', documentId, newContent);
    },
    remove: function (documentId) {
      Meteor.call('comments/remove', documentId);
    },
    like: function (documentId) {
      Meteor.call('comments/like', documentId);
    },
    reply: function (documentId, docScope, content) {
      Meteor.call('comments/reply/add', documentId, docScope, content);
    },
    editReply: function (documentId, docScope, content) {
      Meteor.call('comments/reply/edit', documentId, docScope, content);
    },
    removeReply: function (documentId, docScope) {
      Meteor.call('comments/reply/remove', documentId, docScope);
    },
    likeReply: function (documentId, docScope) {
      Meteor.call('comments/reply/like', documentId, docScope);
    },
    session: {
      set: function (key, val) {
        return Session.set('commentsUi_' + key, val);
      },
      get: function (key) {
        return Session.get('commentsUi_' + key);
      },
      equals: function (key, val) {
        return Session.equals('commentsUi_' + key, val);
      }
    },
    changeSchema: function (cb) {
      var currentSchema = collection.simpleSchema().schema(),
        callbackResult = cb(currentSchema),
        newSchema;

      newSchema = callbackResult ? callbackResult : currentSchema;
      !!newSchema && collection.attachSchema(newSchema, { replace: true });
    },
    _collection: collection
  };
})();
<|endoftext|>