<commit_before>var _ = require('lodash'),
  Promise = require('bluebird');

/**
 * Constructs a new Collection, i.e. an object representing a MySQL table.
 * @param {Database} db a Naomi database instance.
 * @param {String} table the name of a table in database.
 * @constructor
 */
function Collection(db, table) {
  this.db = db;
  this.table = table;

  this.columns = {};
  this.primaryKey = [];
  this.uniqueKeys = {};
  this.indexKeys = {};

  this.queryBuilder = new db.engine.QueryBuilder(this);

  db.on('ready', function () {
    this._loadMeta();
  });

  // check if db is already loaded
  if (db.isReady) this._loadMeta();
}

/**
 * Enqueues the designated function until db is ready.
 * @param {Function} fn
 */
Collection.prototype._enqueue = function (fn) {
  if (this.db.isReady) {
    fn();
  } else {
    this.db.on('ready', fn);
  }
};

/**
 * Loads metadata from database.
 * @private
 */
Collection.prototype._loadMeta = function () {
  var meta = this.db.getTableMeta(this.table);

  if (meta) {
    this.columns = meta.columns;
    this.primaryKey = meta.primaryKey;
    this.uniqueKeys = meta.uniqueKeys;
    this.indexKeys = meta.indexKeys;
  }
};

/**
 * Indicates whether the specified column exists in the collection.
 * Please note: this method is meant to be called after the database is ready.
 * @param {String} column the name of the column.
 * @returns {Boolean}
 * @example
 *
 * collection.hasColumn('name');
 */
Collection.prototype.hasColumn = function (column) {
  return this.columns.hasOwnProperty(column);
};

/**
 * Indicates whether the specified column(s) represent a primary key.
 * Primary keys may be compound, i.e. composed of multiple columns, hence the acceptance of multiple params in this function.
 * Please note: this method is meant to be called after the database is ready.
 * @param {...String} columns the name of the columns.
 * @returns {Boolean}
 * @example
 *
 * collection.isPrimaryKey('id');
 */
Collection.prototype.isPrimaryKey = function () {
  var columns = Array.prototype.slice.call(arguments, 0);
  return _.xor(this.primaryKey, columns).length === 0;
};

/**
 * Indicates whether the specified column(s) represent a unique key.
 * Unique keys may be compound, i.e. composed of multiple columns, hence the acceptance of multiple params.
 * Please note: this method is meant to be called after the database is ready.
 * @param {...String} columns the name of the columns.
 * @returns {Boolean}
 * @example
 *
 * collection.isUniqueKey('pid');
 */
Collection.prototype.isUniqueKey = function () {
  var columns = Array.prototype.slice.call(arguments, 0),
    verdict = false;

  _.forOwn(this.uniqueKeys, function (v) {
    verdict = _.xor(v, columns).length === 0;
    return !verdict; // exit once verdict is true (return false breaks the loop)
  });

  return verdict;
};

/**
 * Indicates whether the specified column(s) represent an index key.
 * Index keys may be compound, i.e. composed of multiple columns, hence the acceptance of multiple params.
 * Please note: this method is meant to be called after the database is ready.
 * @param {...String} columns the name of the columns.
 * @returns {Boolean}
 * @example
 *
 * collection.isIndexKey('firstName', 'lastName');
 */
Collection.prototype.isIndexKey = function () {
  var columns = Array.prototype.slice.call(arguments, 0),
    verdict = false;

  _.forOwn(this.indexKeys, function (v) {
    verdict = _.xor(v, columns).length === 0;
    return !verdict; // exit once verdict is true (return false breaks the loop)
  });

  return verdict;
};

/**
 * Retrieves the designated record(s) from database.
 * @param {Boolean|Number|String|Date|Object|Array<Object>|Null} selector a selector to match the record(s) in database.
 * @param {Object} [options]
 * @param {Function} [callback] a callback function i.e. function(err, data).
 * @throws {Error} if parameters are invalid.
 * @returns {Promise}
 */
Collection.prototype.get = function (selector, options, callback) {
  var query, type;

  // postpone if not ready
  if (!this.db.isReady) {
    this._enqueue(this.get.bind(this, selector, options, callback));
    return;
  }

  // make sure table exists
  if (!this.db.hasTable(this.table)) {
    return Promise.reject('Table "' + this.table + '" cannot be found in database')
      .nodeify(callback);
  }

  // validate "selector" param
  type = typeof(selector);

  if (
    type !== 'boolean' &&
    type !== 'number' &&
    type !== 'string' &&
    selector !== null &&
    !_.isPlainObject(selector) &&
    !Array.isArray(selector) &&
    !_.isDate(selector)
  ) {
    throw new Error('Invalid selector parameter');
  }

  // handle optional "options" param
  if (!_.isPlainObject(options)) {
    type = typeof(options);

    if (type === 'function') {
      callback = options;
    } else if (type !== 'undefined') {
      throw new Error('Invalid options parameter - expected object, received ' + type);
    }

    options = {};
  }

  // validate "callback" param
  type = typeof(callback);

  if (type !== 'function' && type !== 'undefined') {
    throw new Error('Invalid callback parameter - expected function, received ' + type);
  }

  // compile select statement
  try {
    query = this.queryBuilder.compileSelectSQL(selector, options);
  } catch (err) {
    return callback(err.message);
  }

  // run Forrest, run
  return this.db.query(query.sql, query.params)
    .nodeify(callback);
};

/**
 * Retrieves all record(s) from database.
 * This is no more that a handy alias to #get(null, options, callback).
 * @param {Object} [options]
 * @param {Function} [callback] a callback function i.e. function(err, data).
 * @returns {Promise}
 */
Collection.prototype.getAll = function (options, callback) {
  return this.get(null, options, callback);
};

/**
 * Counts the designated record(s) in database.
 * @param {Boolean|Number|String|Date|Object|Array<Object>|Null} selector a selector to match the record(s) in database.
 * @param {Object} [options]
 * @param {Function} [callback] a callback function i.e. function(err, count).
 * @returns {Promise}
 */
Collection.prototype.count = function (selector, options, callback) {
  var query, type;

  // postpone if not ready
  if (!this.db.isReady) {
    this._enqueue(this.count.bind(this, selector, callback));
    return;
  }

  // make sure table exists
  if (!this.db.hasTable(this.table)) {
    return Promise.reject('Table "' + this.table + '" cannot be found in database')
      .nodeify(callback);
  }

  // validate "selector" param
  type = typeof(selector);

  if (
    type !== 'boolean' &&
    type !== 'number' &&
    type !== 'string' &&
    selector !== null &&
    !_.isDate(selector) &&
    !_.isPlainObject(selector) &&
    !_.isArray(selector)
  ) {
    throw new Error('Invalid selector parameter');
  }

  // handle optional "options" param
  if (!_.isPlainObject(options)) {
    type = typeof(options);

    if (type === 'function') {
      callback = options;
    } else if (type !== 'undefined') {
      throw new Error('Invalid options parameter - expected object, received ' + type);
    }

    options = {};
  }

  // validate "callback" param
  type = typeof(callback);

  if (type !== 'function' && type !== 'undefined') {
    throw new Error('Invalid callback parameter - expected function, received ' + type);
  }

  // compile a parameterized SELECT COUNT statement
  try {
    query = this.queryBuilder.compileCountSQL(selector, options);
  } catch (err) {
    return callback(err.message);
  }

  // run Forrest, run
  return this.db.query(query.sql, query.params)
    .then(function (records) {
      return records[0].count; // we need only the number
    })
    .nodeify(callback);
};

/**
 * Counts all record(s) in database.
 * This is no more than a handy alias to #count(null, options, callback).
 * @param {Object} [options]
 * @param {Function} [callback] a callback function i.e. function(err, data).
 * @returns {Promise}
 */
Collection.prototype.countAll = function (options, callback) {
  return this.count(null, options, callback);
};

/**
 * Creates or updates the specified record in database.
 * @param {Array<Object>|Object} attrs the record attributes.
 * @param {Function} [callback] a callback function i.e. function(error, data).
 * @returns {Promise}
 */
Collection.prototype.set = function (attrs, callback) {
  var query;

  // postpone if not ready
  if (!this.db.isReady) {
    this._enqueue(this.set.bind(this, attrs, callback));
    return;
  }

  // make sure table exists
  if (!this.db.hasTable(this.table)) {
    return Promise.reject('Table "' + this.table + '" cannot be found in database')
      .nodeify(callback);
  }

  // compile upsert statement
  try {
    query = this.queryBuilder.compileUpsertSQL(attrs);
  } catch (err) {
    return callback(err.message);
  }

  return this.db.query(query.sql, query.params)
    .nodeify(callback);
};

/**
 * Deletes the designated record(s) from database.
 * @param {Boolean|Number|String|Date|Object|Array.<Object>} selector a selector to match the record(s) in database.
 * @param {Object} [options]
 * @param {Function} callback a callback function i.e. function(error, data).
 */
Collection.prototype.del = function (selector, options, callback) {
  var query, type;

  // postpone if not ready
  if (!this.db.isReady) {
    this._enqueue(this.del.bind(this, selector, callback));
    return;
  }

  // make sure table exists
  if (!this.db.hasTable(this.table)) {
    return Promise.reject('Table "' + this.table + '" cannot be found in database')
      .nodeify(callback);
  }

  // validate "selector" param
  type = typeof(selector);

  if (
    type !== 'boolean' &&
    type !== 'number' &&
    type !== 'string' &&
    !_.isPlainObject(selector) &&
    !Array.isArray(selector) &&
    !_.isDate(selector)
  ) {
    throw new Error('Invalid selector parameter');
  }

  // handle optional "options" param
  if (!_.isPlainObject(options)) {
    type = typeof(options);

    if (type === 'function') {
      callback = options;
    } else if (type !== 'undefined') {
      throw new Error('Invalid options parameter - expected object, received ' + type);
    }

    options = {};
  }

  // validate "callback" param
  type = typeof(callback);

  if (type !== 'function' && type !== 'undefined') {
    throw new Error('Invalid callback parameter - expected function, received ' + type);
  }

  // compile a parameterized DELETE statement
  // compile upsert statement
  try {
    query = this.queryBuilder.compileDeleteSQL(selector, options);
  } catch (err) {
    return callback(err.message);
  }

  return this.db.query(query.sql, query.params)
    .nodeify(callback);
};

// /**
//  * Retrieves the designated record(s) from the given related table from database.
//  * @param {String} table the name of the related table.
//  * @param {Boolean|Number|String|Date|Object|Array.<Object>} [selector] selector to match the record(s) in database.
//  * @param {Function} callback a callback function i.e. function(error, data).
//  */
// Collection.prototype.getRelated = function (table, selector, callback) {
//   var self = this,
//     sql, params, path, whereClause;
//
//   // postpone if not ready
//   if (!this.db.isReady) {
//     this._enqueue(this.getRelated.bind(this, table, selector, callback));
//     return;
//   }
//
//   // make sure collection table exists in db
//   if (!this.db.hasTable(this.table)) return callback(
//     new Error('Table "' + this.table + '" cannot be found in database')
//   );
//
//   // make sure related table exists in db
//   if (!this.db.hasTable(table)) return callback(
//     new Error('Related table "' + table + '" cannot be found in database')
//   );
//
//   // calculate path to related table
//   path = this.db._calculatePath(this.table, table);
//
//   // make sure tables are actually related
//   if (path === null) return callback(
//     new Error('Tables "' + this.table + '" and "' + table + '" are not related; did you forget to set a foreign key?')
//   );
//
//   // compile a parameterized SELECT statement
//   sql = 'SELECT `' + table + '`.* ' +
//     path
//       .map(function (table, i) {
//         var ref, constraints;
//
//         if (i === 0) return 'FROM `' + table + '`';
//
//         ref = path[i - 1];
//         constraints = self.db.getTableMeta(table).related[ref];
//
//         return 'INNER JOIN `' + table + '` ON ' +
//           Object.keys(constraints)
//             .map(function (k) {
//               return '`' + ref + '`.' + k + ' = `' + table + '`.' + constraints[k];
//             })
//             .join(' AND ');
//       })
//       .join(' ');
//   params = [];
//
//   // handle optional "selector" param
//   if (typeof selector === 'function') {
//     callback = selector;
//     selector = null;
//   }
//
//   // append a WHERE clause if selector is specified
//   if (selector) {
//
//     try {
//       whereClause = this._parseSelector(selector, {qualified: true});
//     } catch (err) {
//       return callback(err);
//     }
//
//     sql += ' WHERE ' + whereClause.sql;
//     params.push.apply(params, whereClause.params);
//   }
//
//   sql += ';';
//
//   // run Forrest, run
//   this.db.query(sql, params, callback);
// };

module.exports = Collection;
<commit_msg>Use #once instead of #on to enqueue events.<commit_after>var _ = require('lodash'),
  Promise = require('bluebird');

/**
 * Constructs a new Collection, i.e. an object representing a MySQL table.
 * @param {Database} db a Naomi database instance.
 * @param {String} table the name of a table in database.
 * @constructor
 */
function Collection(db, table) {
  this.db = db;
  this.table = table;

  this.columns = {};
  this.primaryKey = [];
  this.uniqueKeys = {};
  this.indexKeys = {};

  this.queryBuilder = new db.engine.QueryBuilder(this);

  db.on('ready', function () {
    this._loadMeta();
  });

  // check if db is already loaded
  if (db.isReady) this._loadMeta();
}

/**
 * Enqueues the designated function until db is ready.
 * @param {Function} fn
 */
Collection.prototype._enqueue = function (fn) {
  if (this.db.isReady) {
    fn();
  } else {
    this.db.once('ready', fn);
  }
};

/**
 * Loads metadata from database.
 * @private
 */
Collection.prototype._loadMeta = function () {
  var meta = this.db.getTableMeta(this.table);

  if (meta) {
    this.columns = meta.columns;
    this.primaryKey = meta.primaryKey;
    this.uniqueKeys = meta.uniqueKeys;
    this.indexKeys = meta.indexKeys;
  }
};

/**
 * Indicates whether the specified column exists in the collection.
 * Please note: this method is meant to be called after the database is ready.
 * @param {String} column the name of the column.
 * @returns {Boolean}
 * @example
 *
 * collection.hasColumn('name');
 */
Collection.prototype.hasColumn = function (column) {
  return this.columns.hasOwnProperty(column);
};

/**
 * Indicates whether the specified column(s) represent a primary key.
 * Primary keys may be compound, i.e. composed of multiple columns, hence the acceptance of multiple params in this function.
 * Please note: this method is meant to be called after the database is ready.
 * @param {...String} columns the name of the columns.
 * @returns {Boolean}
 * @example
 *
 * collection.isPrimaryKey('id');
 */
Collection.prototype.isPrimaryKey = function () {
  var columns = Array.prototype.slice.call(arguments, 0);
  return _.xor(this.primaryKey, columns).length === 0;
};

/**
 * Indicates whether the specified column(s) represent a unique key.
 * Unique keys may be compound, i.e. composed of multiple columns, hence the acceptance of multiple params.
 * Please note: this method is meant to be called after the database is ready.
 * @param {...String} columns the name of the columns.
 * @returns {Boolean}
 * @example
 *
 * collection.isUniqueKey('pid');
 */
Collection.prototype.isUniqueKey = function () {
  var columns = Array.prototype.slice.call(arguments, 0),
    verdict = false;

  _.forOwn(this.uniqueKeys, function (v) {
    verdict = _.xor(v, columns).length === 0;
    return !verdict; // exit once verdict is true (return false breaks the loop)
  });

  return verdict;
};

/**
 * Indicates whether the specified column(s) represent an index key.
 * Index keys may be compound, i.e. composed of multiple columns, hence the acceptance of multiple params.
 * Please note: this method is meant to be called after the database is ready.
 * @param {...String} columns the name of the columns.
 * @returns {Boolean}
 * @example
 *
 * collection.isIndexKey('firstName', 'lastName');
 */
Collection.prototype.isIndexKey = function () {
  var columns = Array.prototype.slice.call(arguments, 0),
    verdict = false;

  _.forOwn(this.indexKeys, function (v) {
    verdict = _.xor(v, columns).length === 0;
    return !verdict; // exit once verdict is true (return false breaks the loop)
  });

  return verdict;
};

/**
 * Retrieves the designated record(s) from database.
 * @param {Boolean|Number|String|Date|Object|Array<Object>|Null} selector a selector to match the record(s) in database.
 * @param {Object} [options]
 * @param {Function} [callback] a callback function i.e. function(err, data).
 * @throws {Error} if parameters are invalid.
 * @returns {Promise}
 */
Collection.prototype.get = function (selector, options, callback) {
  var query, type;

  // postpone if not ready
  if (!this.db.isReady) {
    this._enqueue(this.get.bind(this, selector, options, callback));
    return;
  }

  // make sure table exists
  if (!this.db.hasTable(this.table)) {
    return Promise.reject('Table "' + this.table + '" cannot be found in database')
      .nodeify(callback);
  }

  // validate "selector" param
  type = typeof(selector);

  if (
    type !== 'boolean' &&
    type !== 'number' &&
    type !== 'string' &&
    selector !== null &&
    !_.isPlainObject(selector) &&
    !Array.isArray(selector) &&
    !_.isDate(selector)
  ) {
    throw new Error('Invalid selector parameter');
  }

  // handle optional "options" param
  if (!_.isPlainObject(options)) {
    type = typeof(options);

    if (type === 'function') {
      callback = options;
    } else if (type !== 'undefined') {
      throw new Error('Invalid options parameter - expected object, received ' + type);
    }

    options = {};
  }

  // validate "callback" param
  type = typeof(callback);

  if (type !== 'function' && type !== 'undefined') {
    throw new Error('Invalid callback parameter - expected function, received ' + type);
  }

  // compile select statement
  try {
    query = this.queryBuilder.compileSelectSQL(selector, options);
  } catch (err) {
    return callback(err.message);
  }

  // run Forrest, run
  return this.db.query(query.sql, query.params)
    .nodeify(callback);
};

/**
 * Retrieves all record(s) from database.
 * This is no more that a handy alias to #get(null, options, callback).
 * @param {Object} [options]
 * @param {Function} [callback] a callback function i.e. function(err, data).
 * @returns {Promise}
 */
Collection.prototype.getAll = function (options, callback) {
  return this.get(null, options, callback);
};

/**
 * Counts the designated record(s) in database.
 * @param {Boolean|Number|String|Date|Object|Array<Object>|Null} selector a selector to match the record(s) in database.
 * @param {Object} [options]
 * @param {Function} [callback] a callback function i.e. function(err, count).
 * @returns {Promise}
 */
Collection.prototype.count = function (selector, options, callback) {
  var query, type;

  // postpone if not ready
  if (!this.db.isReady) {
    this._enqueue(this.count.bind(this, selector, callback));
    return;
  }

  // make sure table exists
  if (!this.db.hasTable(this.table)) {
    return Promise.reject('Table "' + this.table + '" cannot be found in database')
      .nodeify(callback);
  }

  // validate "selector" param
  type = typeof(selector);

  if (
    type !== 'boolean' &&
    type !== 'number' &&
    type !== 'string' &&
    selector !== null &&
    !_.isDate(selector) &&
    !_.isPlainObject(selector) &&
    !_.isArray(selector)
  ) {
    throw new Error('Invalid selector parameter');
  }

  // handle optional "options" param
  if (!_.isPlainObject(options)) {
    type = typeof(options);

    if (type === 'function') {
      callback = options;
    } else if (type !== 'undefined') {
      throw new Error('Invalid options parameter - expected object, received ' + type);
    }

    options = {};
  }

  // validate "callback" param
  type = typeof(callback);

  if (type !== 'function' && type !== 'undefined') {
    throw new Error('Invalid callback parameter - expected function, received ' + type);
  }

  // compile a parameterized SELECT COUNT statement
  try {
    query = this.queryBuilder.compileCountSQL(selector, options);
  } catch (err) {
    return callback(err.message);
  }

  // run Forrest, run
  return this.db.query(query.sql, query.params)
    .then(function (records) {
      return records[0].count; // we need only the number
    })
    .nodeify(callback);
};

/**
 * Counts all record(s) in database.
 * This is no more than a handy alias to #count(null, options, callback).
 * @param {Object} [options]
 * @param {Function} [callback] a callback function i.e. function(err, data).
 * @returns {Promise}
 */
Collection.prototype.countAll = function (options, callback) {
  return this.count(null, options, callback);
};

/**
 * Creates or updates the specified record in database.
 * @param {Array<Object>|Object} attrs the record attributes.
 * @param {Function} [callback] a callback function i.e. function(error, data).
 * @returns {Promise}
 */
Collection.prototype.set = function (attrs, callback) {
  var query;

  // postpone if not ready
  if (!this.db.isReady) {
    this._enqueue(this.set.bind(this, attrs, callback));
    return;
  }

  // make sure table exists
  if (!this.db.hasTable(this.table)) {
    return Promise.reject('Table "' + this.table + '" cannot be found in database')
      .nodeify(callback);
  }

  // compile upsert statement
  try {
    query = this.queryBuilder.compileUpsertSQL(attrs);
  } catch (err) {
    return callback(err.message);
  }

  return this.db.query(query.sql, query.params)
    .nodeify(callback);
};

/**
 * Deletes the designated record(s) from database.
 * @param {Boolean|Number|String|Date|Object|Array.<Object>} selector a selector to match the record(s) in database.
 * @param {Object} [options]
 * @param {Function} callback a callback function i.e. function(error, data).
 */
Collection.prototype.del = function (selector, options, callback) {
  var query, type;

  // postpone if not ready
  if (!this.db.isReady) {
    this._enqueue(this.del.bind(this, selector, callback));
    return;
  }

  // make sure table exists
  if (!this.db.hasTable(this.table)) {
    return Promise.reject('Table "' + this.table + '" cannot be found in database')
      .nodeify(callback);
  }

  // validate "selector" param
  type = typeof(selector);

  if (
    type !== 'boolean' &&
    type !== 'number' &&
    type !== 'string' &&
    !_.isPlainObject(selector) &&
    !Array.isArray(selector) &&
    !_.isDate(selector)
  ) {
    throw new Error('Invalid selector parameter');
  }

  // handle optional "options" param
  if (!_.isPlainObject(options)) {
    type = typeof(options);

    if (type === 'function') {
      callback = options;
    } else if (type !== 'undefined') {
      throw new Error('Invalid options parameter - expected object, received ' + type);
    }

    options = {};
  }

  // validate "callback" param
  type = typeof(callback);

  if (type !== 'function' && type !== 'undefined') {
    throw new Error('Invalid callback parameter - expected function, received ' + type);
  }

  // compile a parameterized DELETE statement
  // compile upsert statement
  try {
    query = this.queryBuilder.compileDeleteSQL(selector, options);
  } catch (err) {
    return callback(err.message);
  }

  return this.db.query(query.sql, query.params)
    .nodeify(callback);
};

// /**
//  * Retrieves the designated record(s) from the given related table from database.
//  * @param {String} table the name of the related table.
//  * @param {Boolean|Number|String|Date|Object|Array.<Object>} [selector] selector to match the record(s) in database.
//  * @param {Function} callback a callback function i.e. function(error, data).
//  */
// Collection.prototype.getRelated = function (table, selector, callback) {
//   var self = this,
//     sql, params, path, whereClause;
//
//   // postpone if not ready
//   if (!this.db.isReady) {
//     this._enqueue(this.getRelated.bind(this, table, selector, callback));
//     return;
//   }
//
//   // make sure collection table exists in db
//   if (!this.db.hasTable(this.table)) return callback(
//     new Error('Table "' + this.table + '" cannot be found in database')
//   );
//
//   // make sure related table exists in db
//   if (!this.db.hasTable(table)) return callback(
//     new Error('Related table "' + table + '" cannot be found in database')
//   );
//
//   // calculate path to related table
//   path = this.db._calculatePath(this.table, table);
//
//   // make sure tables are actually related
//   if (path === null) return callback(
//     new Error('Tables "' + this.table + '" and "' + table + '" are not related; did you forget to set a foreign key?')
//   );
//
//   // compile a parameterized SELECT statement
//   sql = 'SELECT `' + table + '`.* ' +
//     path
//       .map(function (table, i) {
//         var ref, constraints;
//
//         if (i === 0) return 'FROM `' + table + '`';
//
//         ref = path[i - 1];
//         constraints = self.db.getTableMeta(table).related[ref];
//
//         return 'INNER JOIN `' + table + '` ON ' +
//           Object.keys(constraints)
//             .map(function (k) {
//               return '`' + ref + '`.' + k + ' = `' + table + '`.' + constraints[k];
//             })
//             .join(' AND ');
//       })
//       .join(' ');
//   params = [];
//
//   // handle optional "selector" param
//   if (typeof selector === 'function') {
//     callback = selector;
//     selector = null;
//   }
//
//   // append a WHERE clause if selector is specified
//   if (selector) {
//
//     try {
//       whereClause = this._parseSelector(selector, {qualified: true});
//     } catch (err) {
//       return callback(err);
//     }
//
//     sql += ' WHERE ' + whereClause.sql;
//     params.push.apply(params, whereClause.params);
//   }
//
//   sql += ';';
//
//   // run Forrest, run
//   this.db.query(sql, params, callback);
// };

module.exports = Collection;
<|endoftext|>


<commit_before>"use strict";

// ----------------------------------------------------------------------------
// Usage: node run-tests [--php] [--js] [--python] [exchange] [symbol]

// ----------------------------------------------------------------------------

const fs = require ('fs')
    , log = require ('ololog').handleNodeErrors ().unlimited
    , ansi = require ('ansicolor').nice
    , { spawn, execSync } = require ('child_process')

// ----------------------------------------------------------------------------

const [,, ...args] = process.argv

const keys = {

    '--js': false,      // run JavaScript tests only
    '--php': false,     // run PHP tests only
    '--python': false,  // run Python tests only
    '--python3': false,  // run Python tests only
}

let exchanges = []
let symbol = 'all'
let maxConcurrency = 5 // Number.MAX_VALUE // no limit

for (const arg of args) {
    if (arg.startsWith ('--'))               { keys[arg] = true }
    else if (arg.includes ('/'))             { symbol = arg }
    else if (Number.isFinite (Number (arg))) { maxConcurrency = Number (arg) }
    else                                     { exchanges.push (arg) }
}

// ----------------------------------------------------------------------------

if (!exchanges.length) {

    if (!fs.existsSync ('./exchanges.json')) {

        log.bright.red ('\n\tNo', 'exchanges.json'.white, 'found, please run', 'npm run build'.white, 'to generate it!\n')
        process.exit (1)
    }

    exchanges = require ('./exchanges.json').ids
}

// ----------------------------------------------------------------------------

const sleep = s => new Promise (resolve => setTimeout (resolve, s))
const timeout = (s, promise) => Promise.race ([ promise, sleep (s).then (() => { throw new Error ('Test execution took longer than ' + maxTimeout.toString () + ' milliseconds, testing timed out.') }) ])
const maxTimeout = 1200000 // 20 minutes

// ----------------------------------------------------------------------------

const exec = (bin, ...args) =>

    // a custom version of child_process.exec that captures both stdout and
    // stderr,  not separating them into distinct buffers — so that we can show
    // the same output as if it were running in a terminal.

    timeout (maxTimeout, new Promise (return_ => {

        const ps = spawn (bin, args)

        let output = ''
        let stderr = ''
        let hasWarnings = false

        ps.stdout.on ('data', data => { output += data.toString () })
        ps.stderr.on ('data', data => { output += data.toString (); stderr += data.toString (); hasWarnings = true })

        ps.on ('exit', code => {

            output = ansi.strip (output.trim ())
            stderr = ansi.strip (stderr)

            const regex = /\[[a-z]+?\]/gmi

            let match = undefined
            const warnings = []

            match = regex.exec (output)

            if (match) {
                warnings.push (match[0])
                do {
                    if (match = regex.exec (output)) {
                        warnings.push (match[0])
                    }
                } while (match);
            }

            return_ ({
                failed: code !== 0,
                output,
                hasOutput: output.length > 0,
                hasWarnings: hasWarnings || warnings.length > 0,
                warnings: warnings,
            })
        })

    })).catch (e => ({

        failed: true,
        output: e.message

    })).then (x => Object.assign (x, { hasOutput: x.output.length > 0 }))

// ----------------------------------------------------------------------------

let numExchangesTested = 0

// tests of different languages for the same exchange should be run
// sequentially to prevent the interleaving nonces problem.

// ----------------------------------------------------------------------------

const sequentialMap = async (input, fn) => {

    const result = []
    for (const item of input) { result.push (await fn (item)) }
    return result
}

// ----------------------------------------------------------------------------

const testExchange = async (exchange) => {

    // run tests for all/selected languages (in parallel)

    const args = [exchange, ... (symbol === 'all') ? [] : [ symbol ]]
        , allTests = [
            { language: 'JavaScript', key: '--js',      exec: ['node',      'js/test/test.js',     ...args] },
            { language: 'Python',     key: '--python',  exec: ['python',    'python/test/test.py', ...args] },
            { language: 'Python',     key: '--python3', exec: ['python3',   'python/test/test.py', ...args] },
            { language: 'PHP',        key: '--php',     exec: ['php', '-f', 'php/test/test.php',   ...args] }
        ]
        , selectedTests  = allTests.filter (t => keys[t.key])
        , scheduledTests = selectedTests.length ? selectedTests : allTests
        , completeTests  = await sequentialMap (scheduledTests, async test => Object.assign (test, await exec (...test.exec)))
        , failed         = completeTests.find (test => test.failed)
        , hasWarnings    = completeTests.find (test => test.hasWarnings)
        , warnings       = completeTests.reduce ((total, { warnings }) => total.concat (warnings), [])

    // print interactive log output

    numExchangesTested++

    const percentsDone = ((numExchangesTested / exchanges.length) * 100).toFixed (0) + '%'

    const result = (failed      ? 'FAIL'.red :
                   (hasWarnings ? (warnings.length ? warnings.join (' ') : 'WARN').yellow
                                                                         : 'OK'.green))

    const date = (new Date()).toISOString ()
    log.bright (date, ('[' + percentsDone + ']').dim, 'Testing', exchange.cyan, result)

    // return collected data to main loop

    return {

        exchange,
        failed,
        hasWarnings,
        explain () {
            for (const { language, failed, output, hasWarnings } of completeTests) {
                if (failed || hasWarnings) {

                    if (!failed && output.indexOf('[Skipped]') >= 0)
                        continue;

                    if (failed) { log.bright ('\nFAILED'.bgBrightRed.white, exchange.red,    '(' + language + '):\n') }
                    else        { log.bright ('\nWARN'.yellow.inverse,      exchange.yellow, '(' + language + '):\n') }

                    log.indent (1) (output)
                }
            }
        }
    }
}

// ----------------------------------------------------------------------------

function TaskPool (maxConcurrency) {

    const pending = []
        , queue   = []

    let numActive = 0

    return {

        pending,

        run (task) {

            if (numActive >= maxConcurrency) { // queue task

                return new Promise (resolve => queue.push (() => this.run (task).then (resolve)))

            } else { // execute task

                let p = task ().then (x => {
                    numActive--
                    return (queue.length && (numActive < maxConcurrency))
                                ? queue.shift () ().then (() => x)
                                : x
                })
                numActive++
                pending.push (p)
                return p
            }
        }
    }
}

// ----------------------------------------------------------------------------

async function testAllExchanges () {

    const taskPool = TaskPool (maxConcurrency)
    const results = []

    for (const exchange of exchanges) {
        taskPool.run (() => testExchange (exchange).then (x => results.push (x)))
    }

    await Promise.all (taskPool.pending)

    return results
}

// ----------------------------------------------------------------------------

(async function () {

    log.bright.magenta.noPretty ('Testing'.white, Object.assign (
                                                            { exchanges, symbol, keys },
                                                            maxConcurrency >= Number.MAX_VALUE ? {} : { maxConcurrency }))

    const tested    = await testAllExchanges ()
        , warnings  = tested.filter (t => !t.failed && t.hasWarnings)
        , failed    = tested.filter (t =>  t.failed)
        , succeeded = tested.filter (t => !t.failed && !t.hasWarnings)

    log.newline ()

    warnings.forEach (t => t.explain ())
    failed.forEach (t => t.explain ())

    log.newline ()

    if (failed.length)   { log.noPretty.bright.red    ('FAIL'.bgBrightRed.white, failed.map (t => t.exchange)) }
    if (warnings.length) { log.noPretty.bright.yellow ('WARN'.inverse,           warnings.map (t => t.exchange)) }

    log.newline ()

    log.bright ('All done,', [failed.length    && (failed.length    + ' failed')   .red,
                              succeeded.length && (succeeded.length + ' succeeded').green,
                              warnings.length  && (warnings.length  + ' warnings') .yellow].filter (s => s).join (', '))

    if (failed.length) {

        await sleep (10000) // to fight TravisCI log truncation issue, see https://github.com/travis-ci/travis-ci/issues/8189
        process.exit (1)

    } else {
        process.exit (0)
    }

}) ();

// ----------------------------------------------------------------------------
<commit_msg>run-test.js python 3 / python 3 async edits<commit_after>"use strict";

// ----------------------------------------------------------------------------
// Usage: node run-tests [--php] [--js] [--python] [exchange] [symbol]

// ----------------------------------------------------------------------------

const fs = require ('fs')
    , log = require ('ololog').handleNodeErrors ().unlimited
    , ansi = require ('ansicolor').nice
    , { spawn, execSync } = require ('child_process')

// ----------------------------------------------------------------------------

const [,, ...args] = process.argv

const keys = {

    '--js': false,      // run JavaScript tests only
    '--php': false,     // run PHP tests only
    '--python': false,  // run Python 3 tests only
    '--python-async': false, // run Python 3 async tests only
}

let exchanges = []
let symbol = 'all'
let maxConcurrency = 5 // Number.MAX_VALUE // no limit

for (const arg of args) {
    if (arg.startsWith ('--'))               { keys[arg] = true }
    else if (arg.includes ('/'))             { symbol = arg }
    else if (Number.isFinite (Number (arg))) { maxConcurrency = Number (arg) }
    else                                     { exchanges.push (arg) }
}

// ----------------------------------------------------------------------------

if (!exchanges.length) {

    if (!fs.existsSync ('./exchanges.json')) {

        log.bright.red ('\n\tNo', 'exchanges.json'.white, 'found, please run', 'npm run build'.white, 'to generate it!\n')
        process.exit (1)
    }

    exchanges = require ('./exchanges.json').ids
}

// ----------------------------------------------------------------------------

const sleep = s => new Promise (resolve => setTimeout (resolve, s))
const timeout = (s, promise) => Promise.race ([ promise, sleep (s).then (() => { throw new Error ('Test execution took longer than ' + maxTimeout.toString () + ' milliseconds, testing timed out.') }) ])
const maxTimeout = 1200000 // 20 minutes

// ----------------------------------------------------------------------------

const exec = (bin, ...args) =>

    // a custom version of child_process.exec that captures both stdout and
    // stderr,  not separating them into distinct buffers — so that we can show
    // the same output as if it were running in a terminal.

    timeout (maxTimeout, new Promise (return_ => {

        const ps = spawn (bin, args)

        let output = ''
        let stderr = ''
        let hasWarnings = false

        ps.stdout.on ('data', data => { output += data.toString () })
        ps.stderr.on ('data', data => { output += data.toString (); stderr += data.toString (); hasWarnings = true })

        ps.on ('exit', code => {

            output = ansi.strip (output.trim ())
            stderr = ansi.strip (stderr)

            const regex = /\[[a-z]+?\]/gmi

            let match = undefined
            const warnings = []

            match = regex.exec (output)

            if (match) {
                warnings.push (match[0])
                do {
                    if (match = regex.exec (output)) {
                        warnings.push (match[0])
                    }
                } while (match);
            }

            return_ ({
                failed: code !== 0,
                output,
                hasOutput: output.length > 0,
                hasWarnings: hasWarnings || warnings.length > 0,
                warnings: warnings,
            })
        })

    })).catch (e => ({

        failed: true,
        output: e.message

    })).then (x => Object.assign (x, { hasOutput: x.output.length > 0 }))

// ----------------------------------------------------------------------------

let numExchangesTested = 0

// tests of different languages for the same exchange should be run
// sequentially to prevent the interleaving nonces problem.

// ----------------------------------------------------------------------------

const sequentialMap = async (input, fn) => {

    const result = []
    for (const item of input) { result.push (await fn (item)) }
    return result
}

// ----------------------------------------------------------------------------

const testExchange = async (exchange) => {

    // run tests for all/selected languages (in parallel)

    const args = [exchange, ... (symbol === 'all') ? [] : [ symbol ]]
        , allTests = [
            { language: 'JavaScript',     key: '--js',           exec: ['node',      'js/test/test.js',           ...args] },
            { language: 'Python 3',       key: '--python',       exec: ['python3',   'python/test/test.py',       ...args] },
            { language: 'Python 3 Async', key: '--python-async', exec: ['python3',   'python/test/test.py',       ...args] },
            { language: 'PHP',            key: '--php',          exec: ['php', '-f', 'php/test/test.php',         ...args] }
        ]
        , selectedTests  = allTests.filter (t => keys[t.key])
        , scheduledTests = selectedTests.length ? selectedTests : allTests
        , completeTests  = await sequentialMap (scheduledTests, async test => Object.assign (test, await exec (...test.exec)))
        , failed         = completeTests.find (test => test.failed)
        , hasWarnings    = completeTests.find (test => test.hasWarnings)
        , warnings       = completeTests.reduce ((total, { warnings }) => total.concat (warnings), [])

    // print interactive log output

    numExchangesTested++

    const percentsDone = ((numExchangesTested / exchanges.length) * 100).toFixed (0) + '%'

    const result = (failed      ? 'FAIL'.red :
                   (hasWarnings ? (warnings.length ? warnings.join (' ') : 'WARN').yellow
                                                                         : 'OK'.green))

    const date = (new Date()).toISOString ()
    log.bright (date, ('[' + percentsDone + ']').dim, 'Testing', exchange.cyan, result)

    // return collected data to main loop

    return {

        exchange,
        failed,
        hasWarnings,
        explain () {
            for (const { language, failed, output, hasWarnings } of completeTests) {
                if (failed || hasWarnings) {

                    if (!failed && output.indexOf('[Skipped]') >= 0)
                        continue;

                    if (failed) { log.bright ('\nFAILED'.bgBrightRed.white, exchange.red,    '(' + language + '):\n') }
                    else        { log.bright ('\nWARN'.yellow.inverse,      exchange.yellow, '(' + language + '):\n') }

                    log.indent (1) (output)
                }
            }
        }
    }
}

// ----------------------------------------------------------------------------

function TaskPool (maxConcurrency) {

    const pending = []
        , queue   = []

    let numActive = 0

    return {

        pending,

        run (task) {

            if (numActive >= maxConcurrency) { // queue task

                return new Promise (resolve => queue.push (() => this.run (task).then (resolve)))

            } else { // execute task

                let p = task ().then (x => {
                    numActive--
                    return (queue.length && (numActive < maxConcurrency))
                                ? queue.shift () ().then (() => x)
                                : x
                })
                numActive++
                pending.push (p)
                return p
            }
        }
    }
}

// ----------------------------------------------------------------------------

async function testAllExchanges () {

    const taskPool = TaskPool (maxConcurrency)
    const results = []

    for (const exchange of exchanges) {
        taskPool.run (() => testExchange (exchange).then (x => results.push (x)))
    }

    await Promise.all (taskPool.pending)

    return results
}

// ----------------------------------------------------------------------------

(async function () {

    log.bright.magenta.noPretty ('Testing'.white, Object.assign (
                                                            { exchanges, symbol, keys },
                                                            maxConcurrency >= Number.MAX_VALUE ? {} : { maxConcurrency }))

    const tested    = await testAllExchanges ()
        , warnings  = tested.filter (t => !t.failed && t.hasWarnings)
        , failed    = tested.filter (t =>  t.failed)
        , succeeded = tested.filter (t => !t.failed && !t.hasWarnings)

    log.newline ()

    warnings.forEach (t => t.explain ())
    failed.forEach (t => t.explain ())

    log.newline ()

    if (failed.length)   { log.noPretty.bright.red    ('FAIL'.bgBrightRed.white, failed.map (t => t.exchange)) }
    if (warnings.length) { log.noPretty.bright.yellow ('WARN'.inverse,           warnings.map (t => t.exchange)) }

    log.newline ()

    log.bright ('All done,', [failed.length    && (failed.length    + ' failed')   .red,
                              succeeded.length && (succeeded.length + ' succeeded').green,
                              warnings.length  && (warnings.length  + ' warnings') .yellow].filter (s => s).join (', '))

    if (failed.length) {

        await sleep (10000) // to fight TravisCI log truncation issue, see https://github.com/travis-ci/travis-ci/issues/8189
        process.exit (1)

    } else {
        process.exit (0)
    }

}) ();

// ----------------------------------------------------------------------------
<|endoftext|>


<commit_before>/**
 * 403 (Forbidden) Handler
 *
 * Usage:
 * return res.forbidden();
 * return res.forbidden(err);
 * return res.forbidden(err, 'some/specific/forbidden/view');
 *
 * e.g.:
 * ```
 * return res.forbidden('Access denied.');
 * ```
 */

module.exports = function forbidden (data, options) {

  // Get access to `req`, `res`, & `sails`
  var req = this.req;
  var res = this.res;
  var sails = req._sails;

  // Set status code
  res.status(403);

  // Log error to console
  if (data !== undefined) {
    sails.log.verbose('Sending 403 ("Forbidden") response: \n',data);
  }
  else sails.log.verbose('Sending 403 ("Forbidden") response');

  // Only include errors in response if application environment
  // is not set to 'production'.  In production, we shouldn't
  // send back any identifying information about errors.
  if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) {
    data = undefined;
  }

  // If the user-agent wants JSON, always respond with JSON
  // If views are disabled, revert to json
  if (req.wantsJSON || sails.config.hooks.views === false) {
    return res.jsonx(data);
  }

  // If second argument is a string, we take that to mean it refers to a view.
  // If it was omitted, use an empty object (`{}`)
  options = (typeof options === 'string') ? { view: options } : options || {};

  // Attempt to prettify data for views, if it's a non-error object
  var viewData = data;
  if (!(viewData instanceof Error) && 'object' == typeof viewData) {
    try {
      viewData = require('util').inspect(data, {depth: null});
    }
    catch(e) {
      viewData = undefined;
    }
  }

  // If a view was provided in options, serve it.
  // Otherwise try to guess an appropriate view, or if that doesn't
  // work, just send JSON.
  if (options.view) {
    return res.view(options.view, { data: viewData, title: 'Forbidden' });
  }

  // If no second argument provided, try to serve the default view,
  // but fall back to sending JSON(P) if any errors occur.
  else return res.view('403', { data: viewData, title: 'Forbidden' }, function (err, html) {

    // If a view error occured, fall back to JSON(P).
    if (err) {
      //
      // Additionally:
      // • If the view was missing, ignore the error but provide a verbose log.
      if (err.code === 'E_VIEW_FAILED') {
        sails.log.verbose('res.forbidden() :: Could not locate view for error page (sending JSON instead).  Details: ',err);
      }
      // Otherwise, if this was a more serious error, log to the console with the details.
      else {
        sails.log.warn('res.forbidden() :: When attempting to render error page view, an error occured (sending JSON instead).  Details: ', err);
      }
      return res.jsonx(data);
    }

    return res.send(html);
  });

};

<commit_msg>Fixes an issue with flash messages not appearing when res.forbidden is called<commit_after>/**
 * 403 (Forbidden) Handler
 *
 * Usage:
 * return res.forbidden();
 * return res.forbidden(err);
 * return res.forbidden(err, 'some/specific/forbidden/view');
 *
 * e.g.:
 * ```
 * return res.forbidden('Access denied.');
 * ```
 */

module.exports = function forbidden (data, options) {

  // Get access to `req`, `res`, & `sails`
  var req = this.req;
  var res = this.res;
  var sails = req._sails;

  // Add alert messages
  res.locals.messages = { success: [], error: [], warning: [] };

  // Set status code
  res.status(403);

  // Log error to console
  if (data !== undefined) {
    sails.log.verbose('Sending 403 ("Forbidden") response: \n',data);
  }
  else sails.log.verbose('Sending 403 ("Forbidden") response');

  // Only include errors in response if application environment
  // is not set to 'production'.  In production, we shouldn't
  // send back any identifying information about errors.
  if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) {
    data = undefined;
  }

  // If the user-agent wants JSON, always respond with JSON
  // If views are disabled, revert to json
  if (req.wantsJSON || sails.config.hooks.views === false) {
    return res.jsonx(data);
  }

  // If second argument is a string, we take that to mean it refers to a view.
  // If it was omitted, use an empty object (`{}`)
  options = (typeof options === 'string') ? { view: options } : options || {};

  // Attempt to prettify data for views, if it's a non-error object
  var viewData = data;
  if (!(viewData instanceof Error) && 'object' == typeof viewData) {
    try {
      viewData = require('util').inspect(data, {depth: null});
    }
    catch(e) {
      viewData = undefined;
    }
  }

  // If a view was provided in options, serve it.
  // Otherwise try to guess an appropriate view, or if that doesn't
  // work, just send JSON.
  if (options.view) {
    return res.view(options.view, { data: viewData, title: 'Forbidden' });
  }

  // If no second argument provided, try to serve the default view,
  // but fall back to sending JSON(P) if any errors occur.
  else return res.view('403', { data: viewData, title: 'Forbidden' }, function (err, html) {

    // If a view error occured, fall back to JSON(P).
    if (err) {
      //
      // Additionally:
      // • If the view was missing, ignore the error but provide a verbose log.
      if (err.code === 'E_VIEW_FAILED') {
        sails.log.verbose('res.forbidden() :: Could not locate view for error page (sending JSON instead).  Details: ',err);
      }
      // Otherwise, if this was a more serious error, log to the console with the details.
      else {
        sails.log.warn('res.forbidden() :: When attempting to render error page view, an error occured (sending JSON instead).  Details: ', err);
      }
      return res.jsonx(data);
    }

    return res.send(html);
  });

};

<|endoftext|>


<commit_before>'use strict';

(function (root, factory) {
  // optional AMD https://github.com/umdjs/umd/blob/master/amdWeb.js
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(factory);
  } else {
    // Browser globals
    root.raf = factory();
  }
}(this, function () {
  var rafID, callbacks,
      loop, startLoop;

  callbacks = [];

  loop = function(id){
    var self = this;
    if( !callbacks.length ) return;
    callbacks.forEach(function(callback){
      callback.call(self,id);
    });
    window.requestAnimationFrame(loop);
  };

  startLoop = function(){
    if( callbacks.length === 1){
        window.requestAnimationFrame(loop);
    }
  };

  return {
    on: function(callback){
      callbacks.push(callback);
      startLoop();
    },
    off: function(callback){
      var i = callbacks.indexOf(callback);
      if (i > -1) {
        callbacks.splice(i, 1);
      }
      if( !callbacks.length ) {
        window.cancelAnimationFrame(rafID);
      }
    }
  };
}));
<commit_msg>corrects the namespace for non AMD JS<commit_after>'use strict';

(function (root, factory) {
  // optional AMD https://github.com/umdjs/umd/blob/master/amdWeb.js
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(factory);
  } else {
    // Browser globals
    root.simpleRAF = factory();
  }
}(this, function () {
  var rafID, callbacks,
      loop, startLoop;

  callbacks = [];

  loop = function(id){
    var self = this;
    if( !callbacks.length ) return;
    callbacks.forEach(function(callback){
      callback.call(self,id);
    });
    window.requestAnimationFrame(loop);
  };

  startLoop = function(){
    if( callbacks.length === 1){
        window.requestAnimationFrame(loop);
    }
  };

  return {
    on: function(callback){
      callbacks.push(callback);
      startLoop();
    },
    off: function(callback){
      var i = callbacks.indexOf(callback);
      if (i > -1) {
        callbacks.splice(i, 1);
      }
      if( !callbacks.length ) {
        window.cancelAnimationFrame(rafID);
      }
    }
  };
}));
<|endoftext|>


<commit_before>/**
 * Copyright (C) 2020 3D Repo Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

const moment = require('moment');
const repoLicense = require('./config').config.repoLicense;

const Utils = {};

const CryptoJS = require('crypto-js');

Utils.hashCode = (s) => CryptoJS.MD5(s).toString();

Utils.exitApplication = (errCode = -1) => {
	// eslint-disable-next-line no-process-exit
	process.exit(errCode);
};

Utils.sleep = (ms) => new Promise((resolve) => {
	setTimeout(resolve, ms);
});

Utils.getCurrentDateTimeAsString = () => moment().format('YYYY-MM-DD_HH[h]mm[m]ss[s]');

Utils.gatherProcessInformation = (
	owner,
	model,
	database,
	queue,
	filetype,
	filesize,
) => (
	{
		DateTime: Date.now(),
		Owner: owner,
		Model: model,
		Database: database,
		Queue: queue, // logLabel.label,
		FileType: filetype, // file.split('.').pop().toString(),
		FileSize: filesize, // fileStats.size,
		repoLicense: repoLicense
	});

module.exports = Utils;
<commit_msg>#552 licenseKey is the field name<commit_after>/**
 * Copyright (C) 2020 3D Repo Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

const moment = require('moment');
const repoLicense = require('./config').config.repoLicense;

const Utils = {};

const CryptoJS = require('crypto-js');

Utils.hashCode = (s) => CryptoJS.MD5(s).toString();

Utils.exitApplication = (errCode = -1) => {
	// eslint-disable-next-line no-process-exit
	process.exit(errCode);
};

Utils.sleep = (ms) => new Promise((resolve) => {
	setTimeout(resolve, ms);
});

Utils.getCurrentDateTimeAsString = () => moment().format('YYYY-MM-DD_HH[h]mm[m]ss[s]');

Utils.gatherProcessInformation = (
	owner,
	model,
	database,
	queue,
	filetype,
	filesize,
) => (
	{
		DateTime: Date.now(),
		Owner: owner,
		Model: model,
		Database: database,
		Queue: queue, // logLabel.label,
		FileType: filetype, // file.split('.').pop().toString(),
		FileSize: filesize, // fileStats.size,
		licenseKey: repoLicense
	});

module.exports = Utils;
<|endoftext|>


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

/**
 * @fileoverview Javascript object reader and writer.
 * ported from xml.js
 * @author chris@cd-jackson.com (Chris Jackson)
 */
'use strict';

Blockly.Json = {};

/**
 * Encode a block tree as XML.
 * @param {!Object} workspace The SVG workspace.
 * @return {!Object} XML document.
 */
Blockly.Json.getWorkspace = function (workspace) {
    var width = Blockly.svgSize().width;
    var json = {};
    json.block = [];
    var blocks = workspace.getTopBlocks(true);
    for (var i = 0, block; block = blocks[i]; i++) {
        var element = Blockly.Json.blockToDom_(block);
        var xy = block.getRelativeToSurfaceXY();
        element.x = Blockly.RTL ? width - xy.x : xy.x;
        element.y = xy.y;
        json.block.push(element);
    }
    return json;
};

/**
 * Return a javascript object tree
 * @param {!Blockly.Block} block The root block to encode.
 * @return {!Object} Tree of javascript object elements.
 * @private
 */
Blockly.Json.blockToDom_ = function (block) {
    var element = {};
    element.type = block.type;
    element.id = block.id;
    if (block.mutationToDom) {
        // Custom data for an advanced block.
        var mutation = block.mutationToDom();
        if (mutation) {
            element.mutation = mutation;
        }
    }
    function fieldToDom(field) {
        if (field.name && field.EDITABLE) {
            var container = {};
            container.name = field.name;
            container.value = field.getValue();
            if(element.fields==null)
                element.fields = [];
            element.fields.push(container);
        }
    }

    for (var x = 0, input; input = block.inputList[x]; x++) {
        for (var y = 0, field; field = input.fieldRow[y]; y++) {
            fieldToDom(field);
        }
    }

    if (block.comment) {
        var commentElement = {};
        commentElement.text = block.comment.getText();
        commentElement.pinned = block.comment.isVisible();
        var hw = block.comment.getBubbleSize();
        commentElement.h = hw.height;
        commentElement.w = hw.width;
        element.comment = commentElement;
    }

    var hasValues = false;
    for (var i = 0, input; input = block.inputList[i]; i++) {
        var container = {};
        if (input.type == Blockly.DUMMY_INPUT)
            continue;

        var childBlock = input.connection.targetBlock();
        if (input.type == Blockly.INPUT_VALUE) {
            container.type = 'value';
            hasValues = true;
        } else if (input.type == Blockly.NEXT_STATEMENT) {
            container.type = 'statement';
        }
        if (childBlock) {
            if(element.children == null)
                element.children = [];
            container.name = input.name;
            container.block = Blockly.Json.blockToDom_(childBlock);
            element.children.push(container);
        }
    }
    if (hasValues) {
        element.inline = block.inputsInline;
    }
    if (block.isCollapsed()) {
        element.collapsed = true;
    }
    if (block.disabled) {
        element.disabled = true;
    }
    if (!block.isDeletable()) {
        element.deletable = false;
    }
    if (!block.isMovable()) {
        element.movable = false;
    }
    if (!block.isEditable()) {
        element.editable = false;
    }

    if (block.nextConnection) {
        var nextBlock = block.nextConnection.targetBlock();
        if (nextBlock) {
            element.next = Blockly.Json.blockToDom_(nextBlock);
        }
    }

    return element;
};

/**
 * Decode an XML DOM and create blocks on the workspace.
 * @param {!Blockly.Workspace} workspace The SVG workspace.
 * @param {!Object} json input object array.
 */
Blockly.Json.setWorkspace = function (workspace, json) {
    var width = Blockly.svgSize().width;
    if(json == null || json.block == null)
        return;
    var blocks = [].concat(json.block);
    for (var x = 0; x < blocks.length; x++) {
        var child = blocks[x];
        var block = Blockly.Json.domToBlock(workspace, child);
        var blockX = parseInt(block.x, 10);
        var blockY = parseInt(block.y, 10);
        if (!isNaN(blockX) && !isNaN(blockY)) {
            block.moveBy(Blockly.RTL ? width - blockX : blockX, blockY);
        }
    }
};

/**
 * Decode an XML block tag and create a block (and possibly sub blocks) on the
 * workspace.
 * @param {!Blockly.Workspace} workspace The workspace.
 * @param {!Element} xmlBlock XML block element.
 * @param {boolean=} opt_reuseBlock Optional arg indicating whether to
 *     reinitialize an existing block.
 * @return {!Blockly.Block} The root block created.
 * @private
 */
Blockly.Json.domToBlock = function (workspace, jsonBlock, opt_reuseBlock) {
    var block = null;
    var prototypeName = jsonBlock.type;
    if (!prototypeName) {
        throw 'Block type unspecified: \n';
    }
    var id = jsonBlock.id;
    if (opt_reuseBlock && id) {
        block = Blockly.Block.getById(id, workspace);
        // TODO: The following is for debugging.  It should never actually happen.
        if (!block) {
            throw 'Couldn\'t get Block with id: ' + id;
        }
        var parentBlock = block.getParent();
        // If we've already filled this block then we will dispose of it and then
        // re-fill it.
        if (block.workspace) {
            block.dispose(true, false, true);
        }
        block.fill(workspace, prototypeName);
        block.parent_ = parentBlock;
    } else {
        block = Blockly.Block.obtain(workspace, prototypeName);
    }
    if (!block.svg_) {
        block.initSvg();
    }

    var inline = jsonBlock.inline;
    if (inline != null) {
        block.setInputsInline(this.parseBoolean(inline));
    }
    var disabled = jsonBlock.disabled;
    if (disabled != null) {
        block.setDisabled(this.parseBoolean(disabled));
    }
    var deletable = jsonBlock.deletable;
    if (deletable != null) {
        block.setDeletable(this.parseBoolean(deletable));
    }
    var movable = jsonBlock.movable;
    if (movable != null) {
        block.setMovable(this.parseBoolean(movable));
    }
    var editable = jsonBlock.editable;
    if (editable != null) {
        block.setEditable(this.parseBoolean(editable));
    }

    if (jsonBlock.mutation != null) {
        // Custom data for an advanced block.
        if (block.domToMutation) {
            block.domToMutation(jsonBlock.mutation);
        }
    }

    if (jsonBlock.comment != null) {
        block.setCommentText(jsonBlock.comment.text);
        var visible = jsonBlock.comment.pinned;
        if (visible) {
            block.comment.setVisible(this.parseBoolean(visible));
        }
        var bubbleW = parseInt(jsonBlock.comment.w, 10);
        var bubbleH = parseInt(jsonBlock.comment.h, 10);
        if (!isNaN(bubbleW) && !isNaN(bubbleH)) {
            block.comment.setBubbleSize(bubbleW, bubbleH);
        }
    }

    if (jsonBlock.fields != null) {
        var fields = [].concat(jsonBlock.fields);
        for (var i = 0; i < fields.length; i++) {
            if(fields[i].value != null && fields[i].name != null)
            block.setFieldValue(fields[i].value, fields[i].name);
        }
    }

    var blockChild = null;
    if(jsonBlock.children != null) {
        var children = [].concat(jsonBlock.children);
        for (var x = 0; x < children.length; x++) {
            var child = children[x];
            var input;

            if (child.block != null) {
                    input = block.getInput(child.name);
                    if (!input) {
                        throw 'Input ' + child.name + ' does not exist in block ' + prototypeName;
                    }
                        blockChild = Blockly.Json.domToBlock(workspace, child.block, opt_reuseBlock);
                        if (blockChild.outputConnection) {
                            input.connection.connect(blockChild.outputConnection);
                        } else if (blockChild.previousConnection) {
                            input.connection.connect(blockChild.previousConnection);
                        } else {
                            throw 'Child block does not have output or previous statement.';
                        }
            }
            /*
             // Next
             if (child.next &&
             firstRealGrandchild.nodeName.toLowerCase() == 'block') {
             if (!block.nextConnection) {
             throw 'Next statement does not exist.';
             } else if (block.nextConnection.targetConnection) {
             // This could happen if there is more than one XML 'next' tag.
             throw 'Next statement is already connected.';
             }
             blockChild = Blockly.Json.domToBlock(workspace, firstRealGrandchild,
             opt_reuseBlock);
             if (!blockChild.previousConnection) {
             throw 'Next block does not have previous statement.';
             }
             block.nextConnection.connect(blockChild.previousConnection);
             }*/
        }
    }

    var collapsed = jsonBlock.collapsed;
    if (collapsed != null) {
        block.setCollapsed(collapsed);
    }
    var next = block.nextConnection && block.nextConnection.targetBlock();
    if (next) {
        // Next block in a stack needs to square off its corners.
        // Rendering a child will render its parent.
        next.render();
    } else {
        block.render();
    }
    return block;
};

Blockly.Json.parseBoolean = function (boolIn) {
    if(boolIn == true || boolIn == false)
        return boolIn;
    if(boolIn == 'true')
        return true;
    return false;
}

/**
 * Remove any 'next' block (statements in a stack).
 * @param {!Element} xmlBlock XML block element.
 */
Blockly.Json.deleteNext = function (xmlBlock) {
    for (var x = 0, child; child = xmlBlock.childNodes[x]; x++) {
        if (child.nodeName.toLowerCase() == 'next') {
            xmlBlock.removeChild(child);
            break;
        }
    }
};
<commit_msg>Fixed parsing of collapsed parameter<commit_after>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2012 Google Inc.
 * https://blockly.googlecode.com/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Javascript object reader and writer.
 * ported from xml.js
 * @author chris@cd-jackson.com (Chris Jackson)
 */
'use strict';

Blockly.Json = {};

/**
 * Encode a block tree as XML.
 * @param {!Object} workspace The SVG workspace.
 * @return {!Object} XML document.
 */
Blockly.Json.getWorkspace = function (workspace) {
    var width = Blockly.svgSize().width;
    var json = {};
    json.block = [];
    var blocks = workspace.getTopBlocks(true);
    for (var i = 0, block; block = blocks[i]; i++) {
        var element = Blockly.Json.blockToDom_(block);
        var xy = block.getRelativeToSurfaceXY();
        element.x = Blockly.RTL ? width - xy.x : xy.x;
        element.y = xy.y;
        json.block.push(element);
    }
    return json;
};

/**
 * Return a javascript object tree
 * @param {!Blockly.Block} block The root block to encode.
 * @return {!Object} Tree of javascript object elements.
 * @private
 */
Blockly.Json.blockToDom_ = function (block) {
    var element = {};
    element.type = block.type;
    element.id = block.id;
    if (block.mutationToDom) {
        // Custom data for an advanced block.
        var mutation = block.mutationToDom();
        if (mutation) {
            element.mutation = mutation;
        }
    }
    function fieldToDom(field) {
        if (field.name && field.EDITABLE) {
            var container = {};
            container.name = field.name;
            container.value = field.getValue();
            if(element.fields==null)
                element.fields = [];
            element.fields.push(container);
        }
    }

    for (var x = 0, input; input = block.inputList[x]; x++) {
        for (var y = 0, field; field = input.fieldRow[y]; y++) {
            fieldToDom(field);
        }
    }

    if (block.comment) {
        var commentElement = {};
        commentElement.text = block.comment.getText();
        commentElement.pinned = block.comment.isVisible();
        var hw = block.comment.getBubbleSize();
        commentElement.h = hw.height;
        commentElement.w = hw.width;
        element.comment = commentElement;
    }

    var hasValues = false;
    for (var i = 0, input; input = block.inputList[i]; i++) {
        var container = {};
        if (input.type == Blockly.DUMMY_INPUT)
            continue;

        var childBlock = input.connection.targetBlock();
        if (input.type == Blockly.INPUT_VALUE) {
            container.type = 'value';
            hasValues = true;
        } else if (input.type == Blockly.NEXT_STATEMENT) {
            container.type = 'statement';
        }
        if (childBlock) {
            if(element.children == null)
                element.children = [];
            container.name = input.name;
            container.block = Blockly.Json.blockToDom_(childBlock);
            element.children.push(container);
        }
    }
    if (hasValues) {
        element.inline = block.inputsInline;
    }
    if (block.isCollapsed()) {
        element.collapsed = true;
    }
    if (block.disabled) {
        element.disabled = true;
    }
    if (!block.isDeletable()) {
        element.deletable = false;
    }
    if (!block.isMovable()) {
        element.movable = false;
    }
    if (!block.isEditable()) {
        element.editable = false;
    }

    if (block.nextConnection) {
        var nextBlock = block.nextConnection.targetBlock();
        if (nextBlock) {
            element.next = Blockly.Json.blockToDom_(nextBlock);
        }
    }

    return element;
};

/**
 * Decode an XML DOM and create blocks on the workspace.
 * @param {!Blockly.Workspace} workspace The SVG workspace.
 * @param {!Object} json input object array.
 */
Blockly.Json.setWorkspace = function (workspace, json) {
    var width = Blockly.svgSize().width;
    if(json == null || json.block == null)
        return;
    var blocks = [].concat(json.block);
    for (var x = 0; x < blocks.length; x++) {
        var child = blocks[x];
        var block = Blockly.Json.domToBlock(workspace, child);
        var blockX = parseInt(block.x, 10);
        var blockY = parseInt(block.y, 10);
        if (!isNaN(blockX) && !isNaN(blockY)) {
            block.moveBy(Blockly.RTL ? width - blockX : blockX, blockY);
        }
    }
};

/**
 * Decode an XML block tag and create a block (and possibly sub blocks) on the
 * workspace.
 * @param {!Blockly.Workspace} workspace The workspace.
 * @param {!Element} xmlBlock XML block element.
 * @param {boolean=} opt_reuseBlock Optional arg indicating whether to
 *     reinitialize an existing block.
 * @return {!Blockly.Block} The root block created.
 * @private
 */
Blockly.Json.domToBlock = function (workspace, jsonBlock, opt_reuseBlock) {
    var block = null;
    var prototypeName = jsonBlock.type;
    if (!prototypeName) {
        throw 'Block type unspecified: \n';
    }
    var id = jsonBlock.id;
    if (opt_reuseBlock && id) {
        block = Blockly.Block.getById(id, workspace);
        // TODO: The following is for debugging.  It should never actually happen.
        if (!block) {
            throw 'Couldn\'t get Block with id: ' + id;
        }
        var parentBlock = block.getParent();
        // If we've already filled this block then we will dispose of it and then
        // re-fill it.
        if (block.workspace) {
            block.dispose(true, false, true);
        }
        block.fill(workspace, prototypeName);
        block.parent_ = parentBlock;
    } else {
        block = Blockly.Block.obtain(workspace, prototypeName);
    }
    if (!block.svg_) {
        block.initSvg();
    }

    var inline = jsonBlock.inline;
    if (inline != null) {
        block.setInputsInline(this.parseBoolean(inline));
    }
    var disabled = jsonBlock.disabled;
    if (disabled != null) {
        block.setDisabled(this.parseBoolean(disabled));
    }
    var deletable = jsonBlock.deletable;
    if (deletable != null) {
        block.setDeletable(this.parseBoolean(deletable));
    }
    var movable = jsonBlock.movable;
    if (movable != null) {
        block.setMovable(this.parseBoolean(movable));
    }
    var editable = jsonBlock.editable;
    if (editable != null) {
        block.setEditable(this.parseBoolean(editable));
    }

    if (jsonBlock.mutation != null) {
        // Custom data for an advanced block.
        if (block.domToMutation) {
            block.domToMutation(jsonBlock.mutation);
        }
    }

    if (jsonBlock.comment != null) {
        block.setCommentText(jsonBlock.comment.text);
        var visible = jsonBlock.comment.pinned;
        if (visible) {
            block.comment.setVisible(this.parseBoolean(visible));
        }
        var bubbleW = parseInt(jsonBlock.comment.w, 10);
        var bubbleH = parseInt(jsonBlock.comment.h, 10);
        if (!isNaN(bubbleW) && !isNaN(bubbleH)) {
            block.comment.setBubbleSize(bubbleW, bubbleH);
        }
    }

    if (jsonBlock.fields != null) {
        var fields = [].concat(jsonBlock.fields);
        for (var i = 0; i < fields.length; i++) {
            if(fields[i].value != null && fields[i].name != null)
            block.setFieldValue(fields[i].value, fields[i].name);
        }
    }

    var blockChild = null;
    if(jsonBlock.children != null) {
        var children = [].concat(jsonBlock.children);
        for (var x = 0; x < children.length; x++) {
            var child = children[x];
            var input;

            if (child.block != null) {
                    input = block.getInput(child.name);
                    if (!input) {
                        throw 'Input ' + child.name + ' does not exist in block ' + prototypeName;
                    }
                        blockChild = Blockly.Json.domToBlock(workspace, child.block, opt_reuseBlock);
                        if (blockChild.outputConnection) {
                            input.connection.connect(blockChild.outputConnection);
                        } else if (blockChild.previousConnection) {
                            input.connection.connect(blockChild.previousConnection);
                        } else {
                            throw 'Child block does not have output or previous statement.';
                        }
            }
            /*
             // Next
             if (child.next &&
             firstRealGrandchild.nodeName.toLowerCase() == 'block') {
             if (!block.nextConnection) {
             throw 'Next statement does not exist.';
             } else if (block.nextConnection.targetConnection) {
             // This could happen if there is more than one XML 'next' tag.
             throw 'Next statement is already connected.';
             }
             blockChild = Blockly.Json.domToBlock(workspace, firstRealGrandchild,
             opt_reuseBlock);
             if (!blockChild.previousConnection) {
             throw 'Next block does not have previous statement.';
             }
             block.nextConnection.connect(blockChild.previousConnection);
             }*/
        }
    }

    var collapsed = jsonBlock.collapsed;
    if (collapsed != null) {
        block.setCollapsed(this.parseBoolean(collapsed));
    }
    var next = block.nextConnection && block.nextConnection.targetBlock();
    if (next) {
        // Next block in a stack needs to square off its corners.
        // Rendering a child will render its parent.
        next.render();
    } else {
        block.render();
    }
    return block;
};

Blockly.Json.parseBoolean = function (boolIn) {
    if(boolIn == true || boolIn == false)
        return boolIn;
    if(boolIn == 'true')
        return true;
    return false;
}

/**
 * Remove any 'next' block (statements in a stack).
 * @param {!Element} xmlBlock XML block element.
 */
Blockly.Json.deleteNext = function (xmlBlock) {
    for (var x = 0, child; child = xmlBlock.childNodes[x]; x++) {
        if (child.nodeName.toLowerCase() == 'next') {
            xmlBlock.removeChild(child);
            break;
        }
    }
};
<|endoftext|>


<commit_before>/* global describe test expect */

const loop = require('./loop');
const sanitizer = require('../../../sanitizer');
const sanitize = sanitizer;

test('should loop', () => {
  const fields = ['sku', 'active', 'color'];
  const product = {
    sku: 123,
    active: true,
    color: 'green',
  };
  const services = {sanitizer, sanitize};
  const result = loop(fields, product, services);

  expect(result.color).toBe('green');
  expect(result).toBeTruthy();
});

describe('sanitizer', () => {
  test('should ignore null fields', () => {
    const fields = ['sku', 'active', 'color'];
    const product = {
      active: true,
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('color')).toBeFalsy();
    expect(result).toBeTruthy();
  });

  test('should save id as sku', () => {
    const fields = ['sku', 'id'];
    const product = {
      id: 'ID_123',
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('id')).toBeFalsy();
    expect(result.sku).toBe('ID_123');
  });

  test('should save title as name', () => {
    const fields = ['title', 'name'];
    const product = {
      title: 'TITLE_123',
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('title')).toBeFalsy();
    expect(result.name).toBe('TITLE_123');
  });

  test('should sanitize a bunch of keys', () => {
    const fields = ['sku', 'id', 'url', 'link', 'images', 'title', 'name'];
    const product = {
      id: 'ID_123',
      link: 'http://www.link.com',
      images: 'http://www.images.com/12313',
      title: 'TITLE',
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('id')).toBeFalsy();
    expect(result.hasOwnProperty('link')).toBeFalsy();
    expect(result.hasOwnProperty('title')).toBeFalsy();

    expect(result.sku).toBe('ID_123');
    expect(result.url).toBe('http://www.link.com');
    expect(result.images[0]).toBe('http://www.images.com/12313');
    expect(result.name).toBe('TITLE');
  });

  test('should prefer required keys', () => {
    const fields = ['sku', 'id', 'url', 'link', 'images', 'name', 'title'];
    const product = {
      sku: 'SKU_123',
      id: 'ID_123',
      url: 'http://www.url.com',
      link: 'http://www.url.com',
      name: 'NAME',
      title: 'TITLE',
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('id')).toBeFalsy();
    expect(result.hasOwnProperty('link')).toBeFalsy();
    expect(result.hasOwnProperty('title')).toBeFalsy();

    expect(result.sku).toBe('SKU_123');
    expect(result.url).toBe('http://www.url.com');
    expect(result.name).toBe('NAME');
  });

  test('should extract a field with __text from a loop', () => {
    const fields = ['images'];
    const item = {
      images: {
        __aField: 'doesnt matter',
        __text: 'image here',
      },
    };

    const services = {sanitizer, sanitize};
    const result = loop(fields, item, services);

    expect(result.images[0]).toBe('image here');
  });

  test('should work with price and image_link', () => {
    const fields = ['price', 'image_link'];
    const item = {
      price: {
        __text: 'price here',
      },
      image_link: {
        __text: 'image here',
      },
    };

    const services = {sanitizer, sanitize};
    const result = loop(fields, item, services);

    expect(result.image_link[0]).toBe('image here');
    expect(result.price).toBe('price here');
  });
});
<commit_msg>[Tests] Add tests to registerer loop<commit_after>/* global describe test expect */

const loop = require('./loop');
const sanitizer = require('../../../sanitizer');
const sanitize = sanitizer;

test('should loop', () => {
  const fields = ['sku', 'active', 'color'];
  const product = {
    sku: 123,
    active: true,
    color: 'green',
  };
  const services = {sanitizer, sanitize};
  const result = loop(fields, product, services);

  expect(result.color).toBe('green');
  expect(result).toBeTruthy();
});

describe('sanitizer', () => {
  test('should ignore null fields', () => {
    const fields = ['sku', 'active', 'color'];
    const product = {
      active: true,
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('color')).toBeFalsy();
    expect(result).toBeTruthy();
  });

  test('should save id as sku', () => {
    const fields = ['sku', 'id'];
    const product = {
      id: 'ID_123',
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('id')).toBeFalsy();
    expect(result.sku).toBe('ID_123');
  });

  test('should save title as name', () => {
    const fields = ['title', 'name'];
    const product = {
      title: 'TITLE_123',
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('title')).toBeFalsy();
    expect(result.name).toBe('TITLE_123');
  });

  test('should sanitize a bunch of keys', () => {
    const fields = ['sku', 'id', 'url', 'link', 'images', 'title', 'name'];
    const product = {
      id: 'ID_123',
      link: 'http://www.link.com',
      images: 'http://www.images.com/12313',
      title: 'TITLE',
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('id')).toBeFalsy();
    expect(result.hasOwnProperty('link')).toBeFalsy();
    expect(result.hasOwnProperty('title')).toBeFalsy();

    expect(result.sku).toBe('ID_123');
    expect(result.url).toBe('http://www.link.com');
    expect(result.images[0]).toBe('http://www.images.com/12313');
    expect(result.name).toBe('TITLE');
  });

  test('should prefer required keys', () => {
    const fields = ['sku', 'id', 'url', 'link', 'images', 'name', 'title'];
    const product = {
      sku: 'SKU_123',
      id: 'ID_123',
      url: 'http://www.url.com',
      link: 'http://www.url.com',
      name: 'NAME',
      title: 'TITLE',
    };
    const services = {sanitizer, sanitize};
    const result = loop(fields, product, services);

    expect(result.hasOwnProperty('id')).toBeFalsy();
    expect(result.hasOwnProperty('link')).toBeFalsy();
    expect(result.hasOwnProperty('title')).toBeFalsy();

    expect(result.sku).toBe('SKU_123');
    expect(result.url).toBe('http://www.url.com');
    expect(result.name).toBe('NAME');
  });

  test('should extract a field with __text', () => {
    const fields = ['images'];
    const item = {
      images: {
        __aField: 'doesnt matter',
        __text: 'image here',
      },
    };

    const services = {sanitizer, sanitize};
    const result = loop(fields, item, services);

    expect(result.images[0]).toBe('image here');
  });

  test('should work with exceptional fields', () => {
    const fields = ['image_link'];
    const item = {
      image_link: {
        some_ignored_field: 123,
        __text: 'image here',
      },
    };

    const services = {sanitizer, sanitize};
    const result = loop(fields, item, services);

    expect(result.images[0]).toBe('image here');
  });

  test('should work with object with multiple keys containing "__"', () => {
    const fields = ['images'];
    const item = {
      images: {
        __ignored: 'ignored',
        __text: 'Text',
      },
    };

    const services = {sanitizer, sanitize};
    const result = loop(fields, item, services);

    expect(result.images[0]).toBe('Text');
  });

  // TODO: Make tests to sanitize special fields
  describe('special fields', () => {
    describe('images', () => {
      const fields = ['images'];
      test('should sanitize __texts', () => {
        const item = {
          images: {
            __text: 'image',
          },
        };
        const services = {sanitizer, sanitize};
        const result = loop(fields, item, services);

        expect(result.images[0]).toBe('image');
      });
    });
    describe('image_link', () => {
      const fields = ['image_link'];
      test('should sanitize __texts', () => {
        const item = {
          image_link: {
            __text: 'image',
          },
        };
        const services = {sanitizer, sanitize};
        const result = loop(fields, item, services);

        expect(result.images[0]).toBe('image');
      });
    });
  });
  // test('should sanitize image_link');
  // test('should sanitize categories');
  // test('should sanitize id');
  // test('should sanitize price');
  // test('should sanitize installment');
  // test('should sanitize sku');
});
<|endoftext|>


<commit_before>var Discord = require("discord.js");
var config = require("./config");

var mybot = new Discord.Client();
var myUser;
mybot.on("message", function(message) {
	if(message.content === "ping")
	{
		mybot.reply(message, "pong");
	}
	if(message.content === "!help")
	{
		var msg = "You need help " + message.author;
		mybot.sendMessage(message.channel, msg);
	}
	if(message.content.toLowerCase().indexOf("hello") > -1 && message.isMentioned(myUser))
	{
		var msg =  "Hello " + message.author;
		mybot.sendMessage(message.channel, msg);
	}
	if(message.content === "!join")
	{
		mybot.joinVoiceChannel(message.author.voiceChannel, function(error, voiceConnection)
		{
			if(error)
			{
				console.log(error);
				return;
			}
			mybot.voiceConnection.playFile("sound.mp3", function(error, intent)
			{
				if(error)
				{
					console.log(error);
				}
				intent.once("end", playbackFinished());
			});
		});
	}
});

function playbackFinished()
{
	setTimeout(function(){mybot.leaveVoiceChannel(myUser.voiceChannel)}, 5000);
}

mybot.on("ready", function()
{
	mybot.setPlayingGame("with your emotions");
	myUser = mybot.users.get("username", "DeixBot");
	for (var i = 0; i < mybot.servers.length; i++)
	{
		var server = mybot.servers[i];
		var msg = "S'up " + server.name + "!";
		mybot.sendMessage(server.defaultChannel, msg);
	}
});

process.on("SIGINT", function()
{
	for (var i = 0; i < mybot.servers.length; i++)
	{
		var server = mybot.servers[i];
		mybot.sendMessage(server.defaultChannel, "Peace out!", function(i){
			if(i + 1 == mybot.servers.length)
			{
				mybot.logout();
			}
		});
	}
});

mybot.loginWithToken(config.discord.key);
<commit_msg>Possible fix to audio problems<commit_after>var Discord = require("discord.js");
var config = require("./config");

var mybot = new Discord.Client();
var myUser;
mybot.on("message", function(message) {
	if(message.content == "ping")
	{
		mybot.reply(message, "pong");
	}
	if(message.content == "!help")
	{
		var msg = "You need help " + message.author;
		mybot.sendMessage(message.channel, msg);
	}
	if(message.content.toLowerCase().indexOf("hello") > -1 && message.isMentioned(myUser))
	{
		var msg =  "Hello " + message.author;
		mybot.sendMessage(message.channel, msg);
	}
	if(message.content == "!join")
	{
		mybot.joinVoiceChannel(message.author.voiceChannel, function(error, voiceConnection)
		{
			if(error)
			{
				console.error(error);
			}
			mybot.voiceConnection.playFile("sound.mp3", function(error, intent)
			{
				if(error)
				{
					console.error(error);
				}
				intent.once("end", playbackFinished);
			});
		});
	}
});

function playbackFinished()
{
	setTimeout(function(){mybot.leaveVoiceChannel(myUser.voiceChannel)}, 5000);
}

mybot.on("ready", function()
{
	mybot.setPlayingGame("with your emotions");
	myUser = mybot.users.get("username", "DeixBot");
	for (var i = 0; i < mybot.servers.length; i++)
	{
		var server = mybot.servers[i];
		var msg = "S'up " + server.name + "!";
		mybot.sendMessage(server.defaultChannel, msg);
	}
});

process.on("SIGINT", function()
{
	for (var i = 0; i < mybot.servers.length; i++)
	{
		var server = mybot.servers[i];
		mybot.sendMessage(server.defaultChannel, "Peace out!", function(i){
			if(i + 1 == mybot.servers.length)
			{
				mybot.logout();
			}
		});
	}
});

mybot.loginWithToken(config.discord.key);
<|endoftext|>


<commit_before>/*globals window, document */

var Backbone      = require('backbone'),
    Features      = require('lib/features'),
    $             = require('jquery'),
    _             = require('underscore'),
    gmaps         = require('google-maps'),
    fetchLocation = require('cloud/lib/fetch_location');

var DetailView = Backbone.View.extend({
  el: $("#linksf"),
  template: require('templates/detail'),
  events: {
    "render.done": 'setMap',
    "click .directions": 'launchDirections'
  },

  render: function() {
    var facility = this.model;
    var $mapdiv =  this.$('#location-map');

    $(this.el).html(this.template({facility: facility, isMobile: Features.isMobile() }));
    _.defer( function( view ){ view.setMap();}, this );

    return this;
  },

  launchDirections: function() {
    var urlBase = Features.isMobile() ? "comgooglemaps://?daddr=" : 'https://maps.google.com?daddr=',
        dAddr = encodeURIComponent(this.model.address +
                                   "@" +
                                   this.model.location.latitude +
                                   "," +
                                   this.model.location.longitude),
        url = urlBase + dAddr;

    document.location = url;
    return false;
  },

  setMap: function(){
    if(this.$('#location-map')) {
      var location = new gmaps.LatLng(this.model.location.latitude,
                                      this.model.location.longitude);
      var mapOptions = {
            center: location,
            zoom: 15,
            mapTypeId: gmaps.MapTypeId.ROADMAP,
            scrollwheel: false,
            streetViewControl: false,
            zoomControlOptions: {
              style: gmaps.ZoomControlStyle.SMALL
            }
          };
      var map = new gmaps.Map(this.$('#location-map')[0], mapOptions);

      new gmaps.Marker({map: map,
                        position: location,
                        draggable: false});
      }
      this.layout();
  },
  layout: function(){
  $.each($('.desco'), function(){
    if($(this).text().length > 52){
      $(this).text($(this).text().substr(0,52)+"...");}
    });
  $('.more').click(function(){$(this).parent().next($('.seeMore')).slideToggle();});
  $('.prevrow').last().remove();
  $('#backNav').click(function(){window.history.back();});

  }

});

module.exports = DetailView;
<commit_msg>disables scroll on maps<commit_after>/*globals window, document */

var Backbone      = require('backbone'),
    Features      = require('lib/features'),
    $             = require('jquery'),
    _             = require('underscore'),
    gmaps         = require('google-maps'),
    fetchLocation = require('cloud/lib/fetch_location');

var DetailView = Backbone.View.extend({
  el: $("#linksf"),
  template: require('templates/detail'),
  events: {
    "render.done": 'setMap',
    "click .directions": 'launchDirections'
  },

  render: function() {
    var facility = this.model;
    var $mapdiv =  this.$('#location-map');

    $(this.el).html(this.template({facility: facility, isMobile: Features.isMobile() }));
    _.defer( function( view ){ view.setMap();}, this );

    return this;
  },

  launchDirections: function() {
    var urlBase = Features.isMobile() ? "comgooglemaps://?daddr=" : 'https://maps.google.com?daddr=',
        dAddr = encodeURIComponent(this.model.address +
                                   "@" +
                                   this.model.location.latitude +
                                   "," +
                                   this.model.location.longitude),
        url = urlBase + dAddr;

    document.location = url;
    return false;
  },

  setMap: function(){
    if(this.$('#location-map')) {
      var location = new gmaps.LatLng(this.model.location.latitude,
                                      this.model.location.longitude);
      var mapOptions = {
            center: location,
            zoom: 15,
            mapTypeId: gmaps.MapTypeId.ROADMAP,
            scrollwheel: false,
            navigationControl: false,
            draggable: false,
            streetViewControl: false,
            zoomControlOptions: {
              style: gmaps.ZoomControlStyle.SMALL
            }
          };
      var map = new gmaps.Map(this.$('#location-map')[0], mapOptions);

      new gmaps.Marker({map: map,
                        position: location,
                        draggable: false});
      }
      this.layout();
  },
  layout: function(){
  $.each($('.desco'), function(){
    if($(this).text().length > 52){
      $(this).text($(this).text().substr(0,52)+"...");}
    });
  $('.more').click(function(){$(this).parent().next($('.seeMore')).slideToggle();});
  $('.prevrow').last().remove();
  $('#backNav').click(function(){window.history.back();});

  }

});

module.exports = DetailView;
<|endoftext|>