<commit_before>'use strict';

var csstree = require('css-tree'),
  List = csstree.List,
  stable = require('stable'),
  specificity = require('csso/lib/restructure/prepare/specificity');

/**
 * Flatten a CSS AST to a selectors list.
 *
 * @param {import('css-tree').CssNode} cssAst css-tree AST to flatten
 * @return {Array} selectors
 */
function flattenToSelectors(cssAst) {
  var selectors = [];

  csstree.walk(cssAst, {
    visit: 'Rule',
    enter: function (node) {
      if (node.type !== 'Rule') {
        return;
      }

      var atrule = this.atrule;
      var rule = node;

      node.prelude.children.each(function (selectorNode, selectorItem) {
        var selector = {
          item: selectorItem,
          atrule: atrule,
          rule: rule,
          pseudos: /** @type {{item: any; list: any[]}[]} */ ([]),
        };

        selectorNode.children.each(function (
          selectorChildNode,
          selectorChildItem,
          selectorChildList
        ) {
          if (
            selectorChildNode.type === 'PseudoClassSelector' ||
            selectorChildNode.type === 'PseudoElementSelector'
          ) {
            selector.pseudos.push({
              item: selectorChildItem,
              list: selectorChildList,
            });
          }
        });

        selectors.push(selector);
      });
    },
  });

  return selectors;
}

/**
 * Filter selectors by Media Query.
 *
 * @param {Array} selectors to filter
 * @param {Array} useMqs Array with strings of media queries that should pass (<name> <expression>)
 * @return {Array} Filtered selectors that match the passed media queries
 */
function filterByMqs(selectors, useMqs) {
  return selectors.filter(function (selector) {
    if (selector.atrule === null) {
      return ~useMqs.indexOf('');
    }

    var mqName = selector.atrule.name;
    var mqStr = mqName;
    if (
      selector.atrule.expression &&
      selector.atrule.expression.children.first().type === 'MediaQueryList'
    ) {
      var mqExpr = csstree.generate(selector.atrule.expression);
      mqStr = [mqName, mqExpr].join(' ');
    }

    return ~useMqs.indexOf(mqStr);
  });
}

/**
 * Filter selectors by the pseudo-elements and/or -classes they contain.
 *
 * @param {Array} selectors to filter
 * @param {Array} usePseudos Array with strings of single or sequence of pseudo-elements and/or -classes that should pass
 * @return {Array} Filtered selectors that match the passed pseudo-elements and/or -classes
 */
function filterByPseudos(selectors, usePseudos) {
  return selectors.filter(function (selector) {
    var pseudoSelectorsStr = csstree.generate({
      type: 'Selector',
      children: new List().fromArray(
        selector.pseudos.map(function (pseudo) {
          return pseudo.item.data;
        })
      ),
    });
    return ~usePseudos.indexOf(pseudoSelectorsStr);
  });
}

/**
 * Remove pseudo-elements and/or -classes from the selectors for proper matching.
 *
 * @param {Array} selectors to clean
 * @return {void}
 */
function cleanPseudos(selectors) {
  selectors.forEach(function (selector) {
    selector.pseudos.forEach(function (pseudo) {
      pseudo.list.remove(pseudo.item);
    });
  });
}

/**
 * Compares two selector specificities.
 * extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
 *
 * @param {Array} aSpecificity Specificity of selector A
 * @param {Array} bSpecificity Specificity of selector B
 * @return {number} Score of selector specificity A compared to selector specificity B
 */
function compareSpecificity(aSpecificity, bSpecificity) {
  for (var i = 0; i < 4; i += 1) {
    if (aSpecificity[i] < bSpecificity[i]) {
      return -1;
    } else if (aSpecificity[i] > bSpecificity[i]) {
      return 1;
    }
  }

  return 0;
}

/**
 * Compare two simple selectors.
 *
 * @param {Object} aSimpleSelectorNode Simple selector A
 * @param {Object} bSimpleSelectorNode Simple selector B
 * @return {number} Score of selector A compared to selector B
 */
function compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode) {
  var aSpecificity = specificity(aSimpleSelectorNode),
    bSpecificity = specificity(bSimpleSelectorNode);
  return compareSpecificity(aSpecificity, bSpecificity);
}

function _bySelectorSpecificity(selectorA, selectorB) {
  return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data);
}

/**
 * Sort selectors stably by their specificity.
 *
 * @param {Array} selectors to be sorted
 * @return {Array} Stable sorted selectors
 */
function sortSelectors(selectors) {
  return stable(selectors, _bySelectorSpecificity);
}

/**
 * Convert a css-tree AST style declaration to CSSStyleDeclaration property.
 *
 * @param {import('css-tree').CssNode} declaration css-tree style declaration
 * @return {Object} CSSStyleDeclaration property
 */
function csstreeToStyleDeclaration(declaration) {
  var propertyName = declaration.property,
    propertyValue = csstree.generate(declaration.value),
    propertyPriority = declaration.important ? 'important' : '';
  return {
    name: propertyName,
    value: propertyValue,
    priority: propertyPriority,
  };
}

/**
 * Gets the CSS string of a style element
 *
 * @param {Object} elem style element
 * @return {string|Array} CSS string or empty array if no styles are set
 */
function getCssStr(elem) {
  if (elem.content[0].type === 'text' || elem.content[0].type === 'cdata') {
    return elem.content[0].value;
  }
  return '';
}

/**
 * Sets the CSS string of a style element
 *
 * @param {Object} elem style element
 * @param {string} css string to be set
 * @return {Object} reference to field with CSS
 */
function setCssStr(elem, css) {
  if (elem.content[0].type === 'text' || elem.content[0].type === 'cdata') {
    elem.content[0].value = css;
    return elem.content[0].value;
  }
  return css;
}

module.exports.flattenToSelectors = flattenToSelectors;

module.exports.filterByMqs = filterByMqs;
module.exports.filterByPseudos = filterByPseudos;
module.exports.cleanPseudos = cleanPseudos;

module.exports.compareSpecificity = compareSpecificity;
module.exports.compareSimpleSelectorNode = compareSimpleSelectorNode;

module.exports.sortSelectors = sortSelectors;

module.exports.csstreeToStyleDeclaration = csstreeToStyleDeclaration;

module.exports.getCssStr = getCssStr;
module.exports.setCssStr = setCssStr;
<commit_msg>Fix types<commit_after>'use strict';

var csstree = require('css-tree'),
  List = csstree.List,
  stable = require('stable'),
  specificity = require('csso/lib/restructure/prepare/specificity');

/**
 * Flatten a CSS AST to a selectors list.
 *
 * @param {import('css-tree').CssNode} cssAst css-tree AST to flatten
 * @return {Array} selectors
 */
function flattenToSelectors(cssAst) {
  var selectors = [];

  csstree.walk(cssAst, {
    visit: 'Rule',
    enter: function (node) {
      if (node.type !== 'Rule') {
        return;
      }

      var atrule = this.atrule;
      var rule = node;

      node.prelude.children.each(function (selectorNode, selectorItem) {
        var selector = {
          item: selectorItem,
          atrule: atrule,
          rule: rule,
          pseudos: /** @type {{item: any; list: any[]}[]} */ ([]),
        };

        selectorNode.children.each(function (
          selectorChildNode,
          selectorChildItem,
          selectorChildList
        ) {
          if (
            selectorChildNode.type === 'PseudoClassSelector' ||
            selectorChildNode.type === 'PseudoElementSelector'
          ) {
            selector.pseudos.push({
              item: selectorChildItem,
              list: selectorChildList,
            });
          }
        });

        selectors.push(selector);
      });
    },
  });

  return selectors;
}

/**
 * Filter selectors by Media Query.
 *
 * @param {Array} selectors to filter
 * @param {Array} useMqs Array with strings of media queries that should pass (<name> <expression>)
 * @return {Array} Filtered selectors that match the passed media queries
 */
function filterByMqs(selectors, useMqs) {
  return selectors.filter(function (selector) {
    if (selector.atrule === null) {
      return ~useMqs.indexOf('');
    }

    var mqName = selector.atrule.name;
    var mqStr = mqName;
    if (
      selector.atrule.expression &&
      selector.atrule.expression.children.first().type === 'MediaQueryList'
    ) {
      var mqExpr = csstree.generate(selector.atrule.expression);
      mqStr = [mqName, mqExpr].join(' ');
    }

    return ~useMqs.indexOf(mqStr);
  });
}

/**
 * Filter selectors by the pseudo-elements and/or -classes they contain.
 *
 * @param {Array} selectors to filter
 * @param {Array} usePseudos Array with strings of single or sequence of pseudo-elements and/or -classes that should pass
 * @return {Array} Filtered selectors that match the passed pseudo-elements and/or -classes
 */
function filterByPseudos(selectors, usePseudos) {
  return selectors.filter(function (selector) {
    var pseudoSelectorsStr = csstree.generate({
      type: 'Selector',
      children: new List().fromArray(
        selector.pseudos.map(function (pseudo) {
          return pseudo.item.data;
        })
      ),
    });
    return ~usePseudos.indexOf(pseudoSelectorsStr);
  });
}

/**
 * Remove pseudo-elements and/or -classes from the selectors for proper matching.
 *
 * @param {Array} selectors to clean
 * @return {void}
 */
function cleanPseudos(selectors) {
  selectors.forEach(function (selector) {
    selector.pseudos.forEach(function (pseudo) {
      pseudo.list.remove(pseudo.item);
    });
  });
}

/**
 * Compares two selector specificities.
 * extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
 *
 * @param {Array} aSpecificity Specificity of selector A
 * @param {Array} bSpecificity Specificity of selector B
 * @return {number} Score of selector specificity A compared to selector specificity B
 */
function compareSpecificity(aSpecificity, bSpecificity) {
  for (var i = 0; i < 4; i += 1) {
    if (aSpecificity[i] < bSpecificity[i]) {
      return -1;
    } else if (aSpecificity[i] > bSpecificity[i]) {
      return 1;
    }
  }

  return 0;
}

/**
 * Compare two simple selectors.
 *
 * @param {Object} aSimpleSelectorNode Simple selector A
 * @param {Object} bSimpleSelectorNode Simple selector B
 * @return {number} Score of selector A compared to selector B
 */
function compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode) {
  var aSpecificity = specificity(aSimpleSelectorNode),
    bSpecificity = specificity(bSimpleSelectorNode);
  return compareSpecificity(aSpecificity, bSpecificity);
}

function _bySelectorSpecificity(selectorA, selectorB) {
  return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data);
}

/**
 * Sort selectors stably by their specificity.
 *
 * @param {Array} selectors to be sorted
 * @return {Array} Stable sorted selectors
 */
function sortSelectors(selectors) {
  return stable(selectors, _bySelectorSpecificity);
}

/**
 * Convert a css-tree AST style declaration to CSSStyleDeclaration property.
 *
 * @param {import('css-tree').CssNode} declaration css-tree style declaration
 * @return {Object} CSSStyleDeclaration property
 */
function csstreeToStyleDeclaration(declaration) {
  var propertyName = declaration.property,
    propertyValue = csstree.generate(declaration.value),
    propertyPriority = declaration.important ? 'important' : '';
  return {
    name: propertyName,
    value: propertyValue,
    priority: propertyPriority,
  };
}

/**
 * Gets the CSS string of a style element
 *
 * @param {Object} elem style element
 * @return {string} CSS string or empty array if no styles are set
 */
function getCssStr(elem) {
  if (elem.content[0].type === 'text' || elem.content[0].type === 'cdata') {
    return elem.content[0].value;
  }
  return '';
}

/**
 * Sets the CSS string of a style element
 *
 * @param {Object} elem style element
 * @param {string} css string to be set
 * @return {string} reference to field with CSS
 */
function setCssStr(elem, css) {
  if (elem.content[0].type === 'text' || elem.content[0].type === 'cdata') {
    elem.content[0].value = css;
    return elem.content[0].value;
  }
  return css;
}

module.exports.flattenToSelectors = flattenToSelectors;

module.exports.filterByMqs = filterByMqs;
module.exports.filterByPseudos = filterByPseudos;
module.exports.cleanPseudos = cleanPseudos;

module.exports.compareSpecificity = compareSpecificity;
module.exports.compareSimpleSelectorNode = compareSimpleSelectorNode;

module.exports.sortSelectors = sortSelectors;

module.exports.csstreeToStyleDeclaration = csstreeToStyleDeclaration;

module.exports.getCssStr = getCssStr;
module.exports.setCssStr = setCssStr;
<|endoftext|>


<commit_before>var util = require('util'),
    Tools = require('./tools'),
    SearchCriteria = require('./searchCriteria'),
    ResultHolder = require('./resultHolder'),
    ConditionBuilder = require('./conditionBuilder'),
    Field = require('./field'),
    stringHandler = require('./conditionHandlers/stringHandler'),
    booleanHandler = require('./conditionHandlers/booleanHandler'),
    dateHandler = require('./conditionHandlers/dateHandler'),
    numberHandler = require('./conditionHandlers/numberHandler'),
    refHandler = require('./conditionHandlers/refHandler'),
    PATH_SEPARATOR = '.';

var HANDLERS = module.exports.HANDLERS = {
    String: stringHandler,
    Boolean: booleanHandler,
    Date: dateHandler,
    Number: numberHandler
};
HANDLERS[Field.TYPES.REF_TYPE] = refHandler;

var DEFAULT_CONFIG = {
  conditionHandlers: HANDLERS,
  debug: false,
  verbose: false,
  logger: function(level, args) { console.log.apply(console, args); }
}, configured = false;

var enableVerbose =
module.exports.enableVerbose = Tools.enableVerbose;

var enableDebug =
module.exports.enableDebug = Tools.enableDebug;

var setLogger =
module.exports.setLogger = Tools.setLogger;

var setConditionHandlers =
module.exports.setConditionHandlers = ConditionBuilder.setConditionHandlers;

module.exports.getConditionHandlers = ConditionBuilder.getConditionHandlers;

module.exports.configure = function(config) {
  config = config || {};
  enableVerbose(config.verbose || DEFAULT_CONFIG.verbose);
  enableDebug(config.debug || DEFAULT_CONFIG.debug);
  setLogger(config.logger || DEFAULT_CONFIG.logger);
  setConditionHandlers(config.conditionHandlers ||
      DEFAULT_CONFIG.conditionHandlers);
  configured = true;
}
module.exports.configure();

module.exports.init = function(schema, options) {
  schema.statics.dataTable = dataTable;
}

function dataTable(query, options, callback) {
  Tools.debug("Query:", query);
  var self = this;
  if ('function' == typeof options) {
    callback = options;
    options = {};
  } else options = options || {}
  if ('function' != typeof callback) throw new Error("Missing callback !");
  if ('object'!= typeof query) return callback(new Error("Invalid query !"));
  var searchCriteria = buildSearchCriteria(self, query, options);
  Tools.debug("Search Criteria builded:", searchCriteria.toString());
  var resultHolder = buildResultHolder(query);
  countAllRecords.call(self, searchCriteria, resultHolder,
      function(err, searchCriteria, resultHolder) {
        if (err) return callback(err);
        countFilteredRecords.call(self, searchCriteria, resultHolder,
          function(err, searchCriteria, resultHolder) {
            if (err) return callback(err);
            fetchData.call(self, searchCriteria, resultHolder,
              function(err, searchCriteria, resultHolder) {
                if (err) return callback(err);
                sendData(resultHolder, callback);
              });
          });
      });
}

function sendData(resultHolder, callback) {
  Tools.debug("Data:", resultHolder.toString());
  callback(null, resultHolder.data);
}

/*
 * Build Criteria methods
*/

function buildSearchCriteria(model, query, options) {
  var searchCriteria = new SearchCriteria(model, query, options);
  return searchCriteria;
}

/*
 * Build Data methods
*/

function buildResultHolder(query) {
  var resultHolder = new ResultHolder(query);
  return resultHolder;
}

/*
 * Data Fetch methods
*/

function countAllRecords(searchCriteria, resultHolder, callback) {
  this.count(searchCriteria.options.conditions || {}, function(err, count) {
    if (err) return callback(err);
    resultHolder.totalRecords = count;
    return callback(null, searchCriteria, resultHolder);
  });
}

function countFilteredRecords(searchCriteria, resultHolder, callback) {
  if (searchCriteria.conditions == undefined) {
    resultHolder.totalFilteredRecords = resultHolder.totalRecords;
    return callback(null, searchCriteria, resultHolder);
  }
  this.count(searchCriteria.conditions, function(err, count) {
    if (err) return callback(err);
    resultHolder.totalFilteredRecords = count;
    return callback(null, searchCriteria, resultHolder);
  });
}

function fetchData(searchCriteria, resultHolder, callback) {
  var query = this.aggregate();
  if (searchCriteria.conditions) query.match(searchCriteria.conditions);
  query.skip(searchCriteria.pageStart).limit(searchCriteria.pageSize);
  if (Object.keys(searchCriteria.sort).length > 0)
    query.sort(searchCriteria.sort);
  if (searchCriteria.aggregates.length > 0) {
    searchCriteria.aggregates.forEach(function(aggregate) {
      query.unwind(aggregate.unwind).match(aggregate.match)
        .group(aggregate.group).project(aggregate.project);
    });
  }
  var project = {};
  for (var key in searchCriteria.select)
    project[key] = searchCriteria.select[key];
  if ('object' == typeof searchCriteria.options.select) {
    for (var key in searchCriteria.options.select)
      project[key] = searchCriteria.options.select[key];
  } else if ('string' == typeof searchCriteria.options.select) {
    for (var value in searchCriteria.options.select.split(' ')) {
      if (value.indexOf('+') == 0) project[value.substring(1)] = 1;
      else if (value.indexOf('-') == 0) project[value.substring(1)] = 0;
      else project[value] = 1;
    }
  }
  if (Object.keys(project).length > 0) query.project(project)
  var self = this;
  query.exec(function(err, records) {
    if (err) return callback(err);
    if (searchCriteria.populate.length == 0) {
      resultHolder.records = records;
      return callback(null, searchCriteria, resultHolder);
    }
    self.populate(records, searchCriteria.populate, function(err, records) {
      if (err) return callback(err);
      resultHolder.records = records;
      callback(null, searchCriteria, resultHolder);
    });
  });
}
<commit_msg>Sort before skip and limit in order to preserve initial sort integrity<commit_after>var util = require('util'),
    Tools = require('./tools'),
    SearchCriteria = require('./searchCriteria'),
    ResultHolder = require('./resultHolder'),
    ConditionBuilder = require('./conditionBuilder'),
    Field = require('./field'),
    stringHandler = require('./conditionHandlers/stringHandler'),
    booleanHandler = require('./conditionHandlers/booleanHandler'),
    dateHandler = require('./conditionHandlers/dateHandler'),
    numberHandler = require('./conditionHandlers/numberHandler'),
    refHandler = require('./conditionHandlers/refHandler'),
    PATH_SEPARATOR = '.';

var HANDLERS = module.exports.HANDLERS = {
    String: stringHandler,
    Boolean: booleanHandler,
    Date: dateHandler,
    Number: numberHandler
};
HANDLERS[Field.TYPES.REF_TYPE] = refHandler;

var DEFAULT_CONFIG = {
  conditionHandlers: HANDLERS,
  debug: false,
  verbose: false,
  logger: function(level, args) { console.log.apply(console, args); }
}, configured = false;

var enableVerbose =
module.exports.enableVerbose = Tools.enableVerbose;

var enableDebug =
module.exports.enableDebug = Tools.enableDebug;

var setLogger =
module.exports.setLogger = Tools.setLogger;

var setConditionHandlers =
module.exports.setConditionHandlers = ConditionBuilder.setConditionHandlers;

module.exports.getConditionHandlers = ConditionBuilder.getConditionHandlers;

module.exports.configure = function(config) {
  config = config || {};
  enableVerbose(config.verbose || DEFAULT_CONFIG.verbose);
  enableDebug(config.debug || DEFAULT_CONFIG.debug);
  setLogger(config.logger || DEFAULT_CONFIG.logger);
  setConditionHandlers(config.conditionHandlers ||
      DEFAULT_CONFIG.conditionHandlers);
  configured = true;
}
module.exports.configure();

module.exports.init = function(schema, options) {
  schema.statics.dataTable = dataTable;
}

function dataTable(query, options, callback) {
  Tools.debug("Query:", query);
  var self = this;
  if ('function' == typeof options) {
    callback = options;
    options = {};
  } else options = options || {}
  if ('function' != typeof callback) throw new Error("Missing callback !");
  if ('object'!= typeof query) return callback(new Error("Invalid query !"));
  var searchCriteria = buildSearchCriteria(self, query, options);
  Tools.debug("Search Criteria builded:", searchCriteria.toString());
  var resultHolder = buildResultHolder(query);
  countAllRecords.call(self, searchCriteria, resultHolder,
      function(err, searchCriteria, resultHolder) {
        if (err) return callback(err);
        countFilteredRecords.call(self, searchCriteria, resultHolder,
          function(err, searchCriteria, resultHolder) {
            if (err) return callback(err);
            fetchData.call(self, searchCriteria, resultHolder,
              function(err, searchCriteria, resultHolder) {
                if (err) return callback(err);
                sendData(resultHolder, callback);
              });
          });
      });
}

function sendData(resultHolder, callback) {
  Tools.debug("Data:", resultHolder.toString());
  callback(null, resultHolder.data);
}

/*
 * Build Criteria methods
*/

function buildSearchCriteria(model, query, options) {
  var searchCriteria = new SearchCriteria(model, query, options);
  return searchCriteria;
}

/*
 * Build Data methods
*/

function buildResultHolder(query) {
  var resultHolder = new ResultHolder(query);
  return resultHolder;
}

/*
 * Data Fetch methods
*/

function countAllRecords(searchCriteria, resultHolder, callback) {
  this.count(searchCriteria.options.conditions || {}, function(err, count) {
    if (err) return callback(err);
    resultHolder.totalRecords = count;
    return callback(null, searchCriteria, resultHolder);
  });
}

function countFilteredRecords(searchCriteria, resultHolder, callback) {
  if (searchCriteria.conditions == undefined) {
    resultHolder.totalFilteredRecords = resultHolder.totalRecords;
    return callback(null, searchCriteria, resultHolder);
  }
  this.count(searchCriteria.conditions, function(err, count) {
    if (err) return callback(err);
    resultHolder.totalFilteredRecords = count;
    return callback(null, searchCriteria, resultHolder);
  });
}

function fetchData(searchCriteria, resultHolder, callback) {
  var query = this.aggregate();
  if (searchCriteria.conditions) query.match(searchCriteria.conditions);
  // Sort before skip and limit in order to preserve initial sort integrity
  if (Object.keys(searchCriteria.sort).length > 0)
    query.sort(searchCriteria.sort);
  query.skip(searchCriteria.pageStart).limit(searchCriteria.pageSize);
  if (searchCriteria.aggregates.length > 0) {
    searchCriteria.aggregates.forEach(function(aggregate) {
      query.unwind(aggregate.unwind).match(aggregate.match)
        .group(aggregate.group).project(aggregate.project);
    });
  }
  var project = {};
  for (var key in searchCriteria.select)
    project[key] = searchCriteria.select[key];
  if ('object' == typeof searchCriteria.options.select) {
    for (var key in searchCriteria.options.select)
      project[key] = searchCriteria.options.select[key];
  } else if ('string' == typeof searchCriteria.options.select) {
    for (var value in searchCriteria.options.select.split(' ')) {
      if (value.indexOf('+') == 0) project[value.substring(1)] = 1;
      else if (value.indexOf('-') == 0) project[value.substring(1)] = 0;
      else project[value] = 1;
    }
  }
  if (Object.keys(project).length > 0) query.project(project)
  var self = this;
  query.exec(function(err, records) {
    if (err) return callback(err);
    if (searchCriteria.populate.length == 0) {
      resultHolder.records = records;
      return callback(null, searchCriteria, resultHolder);
    }
    self.populate(records, searchCriteria.populate, function(err, records) {
      if (err) return callback(err);
      resultHolder.records = records;
      callback(null, searchCriteria, resultHolder);
    });
  });
}
<|endoftext|>


<commit_before>/**
 * The datastore itself
 * TODO
 * Improve serialization (for types such as Dates, handle new lines in strings)
 * Serialization and deserialization functions
 * Queue inserts, removes and updates
 * Update and removes should only modify the corresponding part of the database
 */

var fs = require('fs')
  , path = require('path')
  , customUtils = require('./customUtils')
  ;

/**
 * Create a new collection
 */
function Datastore (filename) {
  this.filename = filename;
  this.data = [];
}

/**
 * Load the database
 * For now this means pulling data out of the data file or creating it
 * if it doesn't exist
 * @param {Function} cb Optional callback, signature: err
 */
Datastore.prototype.loadDatabase = function (cb) {
  var callback = cb || function () {}
    , self = this
    ;

  customUtils.ensureDirectoryExists(path.dirname(self.filename), function (err) {
    fs.exists(self.filename, function (exists) {
      if (!exists) {
        self.data = [];
        fs.writeFile(self.filename, '', 'utf8', function (err) { return callback(err); });
      } else {
        fs.readFile(self.filename, 'utf8', function (err, rawData) {
          if (err) { return callback(err); }
          self.data = Datastore.treatRawData(rawData);
          return callback();
        });
      }
    });
  });
};

/**
 * From a database's raw data, return the corresponding
 * machine understandable collection
 */
Datastore.treatRawData = function (rawData) {
  var data = rawData.split('\n')
    , res = [];

  data.forEach(function (d) {
    var doc;

    try {
      doc = JSON.parse(d);
      res.push(doc);
    } catch (e) {
    }
  });

  return res;
};

/**
 * Insert a new document
 * @param {Function} cb Optional callback, signature: err, insertedDoc
 */
Datastore.prototype.insert = function (newDoc, cb) {
  var callback = cb || function () {}
    , self = this
    , persistableNewDoc
    ;

  try {
    newDoc._id = customUtils.uid(16);
    persistableNewDoc = JSON.stringify(newDoc);
  } catch (e) {
    return callback(e);
  }

  fs.appendFile(self.filename, persistableNewDoc + '\n', 'utf8', function (err) {
    if (err) { return callback(err); }

    var insertedDoc = JSON.parse(persistableNewDoc);
    self.data.push(insertedDoc);   // Make sure the doc is the same on the disk and in memory
                                                     // Some docs can't be stringified correctly
    return callback(null, insertedDoc);
  });
};

/**
 * Check whether object is matched by the given query
 */
Datastore.match = function (obj, query) {
  var match = true;

  Object.keys(query).forEach(function (k) {
    if (obj[k] !== query[k]) { match = false; }
  });

  return match;
};

/**
 * Find all documents matching the query
 * @param {Object} query MongoDB-style query
 */
Datastore.prototype.find = function (query, callback) {
  var res = []
    , self = this
    ;

  self.data.forEach(function (d) {
    if (Datastore.match(d, query)) {
      res.push(d);
    }
  });

  return callback(null, res);
};

/**
 * Find one document matching the query
 * @param {Object} query MongoDB-style query
 */
Datastore.prototype.findOne = function (query, callback) {
  var self = this
    , i
    ;

  for (i = 0; i < self.data.length; i += 1) {
    if (Datastore.match(self.data[i], query)) {
      return callback(null, self.data[i]);
    }
  }

  return callback(null, null);
};

/**
 * Persist the whole database
 * @param {Array} data Optional data to persist. If not set, we use the database data
 * @param {Function} cb Optional callback, signature: err
 */
Datastore.prototype.persistWholeDatabase = function (data, cb) {
  var callback
    , self = this
    , newContents = '';

  if (!data) {
    data = self.data;
  }

  if (typeof data === 'function') {
    cb = data;
    data = self.data;
  }

  callback = cb || function () {};

  data.forEach(function (d) {
    newContents += JSON.stringify(d) + '\n';
  });

  fs.writeFile(self.filename, newContents, 'utf8', callback);
};

/**
 * Update all docs matching query
 * For now, very naive implementation (recalculating the whole database)
 * @param {Object} query
 * @param {Object} newDoc Will replace the former docs
 * @param {Function} cb Optional callback, signature: err, numReplaced
 */
Datastore.prototype.update = function (query, newDoc, cb) {
  var callback = cb || function () {}
    , self = this
    , numReplaced = 0
    , newData = [];

  self.data.forEach(function (d) {
    if (Datastore.match(d, query)) {
      numReplaced += 1;
      newData.push(newDoc);
    } else {
      newData.push(d);
    }
  });

  self.persistWholeDatabase(newData, function (err) {
    if (err) { return callback(err); }
    self.data = newData;
    return callback(null, numReplaced);
  });
};

var d = new Datastore('workspace/test.db');
d.loadDatabase(function (err) {
  console.log(d.data);

  d.update({ yahoo: 'dailymotion' }, {yahoo: 'great' }, function (err, n) {
    console.log("--------------");
    console.log(err);
    console.log(n);
  });

});

<commit_msg>Able to choose whether to update multiple docs or just one<commit_after>/**
 * The datastore itself
 * TODO
 * Improve serialization (for types such as Dates, handle new lines in strings)
 * Serialization and deserialization functions
 * Queue inserts, removes and updates
 * Update and removes should only modify the corresponding part of the database
 */

var fs = require('fs')
  , path = require('path')
  , customUtils = require('./customUtils')
  ;

/**
 * Create a new collection
 */
function Datastore (filename) {
  this.filename = filename;
  this.data = [];
}

/**
 * Load the database
 * For now this means pulling data out of the data file or creating it
 * if it doesn't exist
 * @param {Function} cb Optional callback, signature: err
 */
Datastore.prototype.loadDatabase = function (cb) {
  var callback = cb || function () {}
    , self = this
    ;

  customUtils.ensureDirectoryExists(path.dirname(self.filename), function (err) {
    fs.exists(self.filename, function (exists) {
      if (!exists) {
        self.data = [];
        fs.writeFile(self.filename, '', 'utf8', function (err) { return callback(err); });
      } else {
        fs.readFile(self.filename, 'utf8', function (err, rawData) {
          if (err) { return callback(err); }
          self.data = Datastore.treatRawData(rawData);
          return callback();
        });
      }
    });
  });
};

/**
 * From a database's raw data, return the corresponding
 * machine understandable collection
 */
Datastore.treatRawData = function (rawData) {
  var data = rawData.split('\n')
    , res = [];

  data.forEach(function (d) {
    var doc;

    try {
      doc = JSON.parse(d);
      res.push(doc);
    } catch (e) {
    }
  });

  return res;
};

/**
 * Insert a new document
 * @param {Function} cb Optional callback, signature: err, insertedDoc
 */
Datastore.prototype.insert = function (newDoc, cb) {
  var callback = cb || function () {}
    , self = this
    , persistableNewDoc
    ;

  try {
    newDoc._id = customUtils.uid(16);
    persistableNewDoc = JSON.stringify(newDoc);
  } catch (e) {
    return callback(e);
  }

  fs.appendFile(self.filename, persistableNewDoc + '\n', 'utf8', function (err) {
    if (err) { return callback(err); }

    var insertedDoc = JSON.parse(persistableNewDoc);
    self.data.push(insertedDoc);   // Make sure the doc is the same on the disk and in memory
                                                     // Some docs can't be stringified correctly
    return callback(null, insertedDoc);
  });
};

/**
 * Check whether object is matched by the given query
 */
Datastore.match = function (obj, query) {
  var match = true;

  Object.keys(query).forEach(function (k) {
    if (obj[k] !== query[k]) { match = false; }
  });

  return match;
};

/**
 * Find all documents matching the query
 * @param {Object} query MongoDB-style query
 */
Datastore.prototype.find = function (query, callback) {
  var res = []
    , self = this
    ;

  self.data.forEach(function (d) {
    if (Datastore.match(d, query)) {
      res.push(d);
    }
  });

  return callback(null, res);
};

/**
 * Find one document matching the query
 * @param {Object} query MongoDB-style query
 */
Datastore.prototype.findOne = function (query, callback) {
  var self = this
    , i
    ;

  for (i = 0; i < self.data.length; i += 1) {
    if (Datastore.match(self.data[i], query)) {
      return callback(null, self.data[i]);
    }
  }

  return callback(null, null);
};

/**
 * Persist the whole database
 * @param {Array} data Optional data to persist. If not set, we use the database data
 * @param {Function} cb Optional callback, signature: err
 */
Datastore.prototype.persistWholeDatabase = function (data, cb) {
  var callback
    , self = this
    , newContents = '';

  if (!data) {
    data = self.data;
  }

  if (typeof data === 'function') {
    cb = data;
    data = self.data;
  }

  callback = cb || function () {};

  data.forEach(function (d) {
    newContents += JSON.stringify(d) + '\n';
  });

  fs.writeFile(self.filename, newContents, 'utf8', callback);
};

/**
 * Update all docs matching query
 * For now, very naive implementation (recalculating the whole database)
 * @param {Object} query
 * @param {Object} newDoc Will replace the former docs
 * @param {Object} options Optional options
 *                 options.multi If true, can update multiple documents (defaults to false)
 * @param {Function} cb Optional callback, signature: err, numReplaced
 */
Datastore.prototype.update = function (query, newDoc, options, cb) {
  var callback
    , self = this
    , numReplaced = 0
    , multi
    , newData = [];

  if (typeof options === 'function') { cb = options; options = {}; }
  callback = cb || function () {};
  multi = options.multi !== undefined ? options.multi : false;

  self.data.forEach(function (d) {
    if (Datastore.match(d, query) && (multi || numReplaced === 0)) {
      numReplaced += 1;
      newData.push(newDoc);
    } else {
      newData.push(d);
    }
  });

  self.persistWholeDatabase(newData, function (err) {
    if (err) { return callback(err); }
    self.data = newData;
    return callback(null, numReplaced);
  });
};

var d = new Datastore('workspace/test.db');
d.loadDatabase(function (err) {
  console.log(d.data);

  d.update({ yahoo: 'great' }, {yahoo: 'great2' }, { multi: false }, function (err, n) {
    console.log("--------------");
    console.log(err);
    console.log(n);
  });

});

<|endoftext|>


<commit_before>/**
 * Created by ozlevka on 3/21/14.
 */
require('date-utils');
var mysql = require('mysql');
var config = require('../config/config');
var fs = require('fs');
var async = require('async');
var winston = require('winston');
var util = require('util');
var sqlsConfig = require('../config/sqls');

var logger = new (winston.Logger)({
    transports : [
        new (winston.transports.Console)(config.logger.console),
        new (winston.transports.File)(config.logger.file)
    ]
});

var pool = mysql.createPool(config.mysql);

exports = module.exports;

/*
    Process single query
*/
function processQuery(endpoint, sql, params, callback) {
    pool.getConnection(function(err, connection){
        if(err) {
            logger.log('error', util.format('%s getConnection failed', endpoint) , util.inspect(err));
            callback(err);
        } else {
            var cb = function(err, results){
                if(err) {
                    logger.log('error', '%s query failed for params=%s', util.inspect(err), util.inspect(params));
                    callback(err);
                } else {
                    logger.debug(util.format('%s suces process sql= %s, params=  %s', endpoint, sql, util.inspect(params)));
                    callback(null, results);
                }
                connection.release();
            };
            if(!params) connection.query(sql,cb);
            else connection.query(sql, params, cb);
        }
    })
}

function createQueryFromObject(obj) {
    var columns = Object.keys(obj);
    var values = [];

    for(var i = 0; i < columns.length; i++) {
        values.push(obj[columns[i]]);
    }

    return {
        columns : columns,
        values : values
    }
}

/*
* Process number sqls in transation;
* */
function processTransaction(sqls, callback) {
    pool.getConnection(function(err, connection){
        if(err) {
            logger.log('error', 'processTransaction getConnection', util.inspect(err));
            callback(err);
        }
        else {
            connection.beginTransaction(function(err){
                if(err) {
                    logger.log('processTransaction beginTransaction', util.inspect(err));
                    callback(err);
                }
                else {
                    async.map(sqls, function(sql, callback) {
                        logger.debug(util.format('process query %s', sql));
                        connection.query(sql,function(err, res) {
                            if(err) {
                                logger.log('error', util.format('processTransaction executeQuery: %s', sql), util.inspect(err));
                                callback(err);
                            } else {
                                logger.debug(util.format('query %s success', sql));
                                callback(null, res);
                            }
                        });
                    }, function(err, results){
                        if(err) {
                            connection.rollback(function() {
                                logger.log('error','processTransaction connectionRolled back', util.inspect(err));
                                callback(err);
                            })
                        }
                        else {
                            connection.commit(function(err) {
                                if(err) {
                                    logger.log('error', 'processTransaction commit failed', util.inspect(err));
                                    connection.rollback(function(){
                                        callback(err);
                                    });
                                } else {
                                    logger.debug('processTransaction success');
                                    callback(null,results)
                                }
                            });
                        }
                    });
                }
            });
        }
    });
};

function parseTimeFromString(time) {
    if(typeof time == 'string') {
        var splittedTime = time.split(':');
        for(var i = 0; i < splittedTime.length; i++) {
            splittedTime[i] = splittedTime[i] * 1;
        }

        return splittedTime[0] * 60 + splittedTime[1];
    }

    return 0;
}

exports.releasePool = function() {
    pool.end(function(err){
        if(err) {
            logger.log('error', 'releasePool', err);
        }
    })
}

exports.login = function(user_name, password, callback) {
    if(!user_name || !password) {
        logger.error('login user_name or password undefined');
        callback(new Error("user_name or password undefined"));
    } else {
        async.waterfall([
            function(cb) {
                processQuery('login', sqlsConfig.loginGetUser,[user_name, password], function(e, results) {
                    if(e) cb(e);
                    else {
                        cb(null,results);
                    }
                })
            },
            function(user_res, cb) {
                if(!user_res || user_res.length == 0) {
                    logger.error('login user %s password %s not found', user_name, password);
                    cb(new Error('user not found'));
                } else {
                    cb(null,user_res[0].id);
                }
            },
            function(user_id, cb) {
                    if(!user_id) cb(new Error('strange ' + user_id + ' user_id undefined'));
                else {
                    processQuery('login', sqlsConfig.getToken,[user_id, config.user.tokenExpiredIn], function(err, res){
                        if(err) {
                            logger.error('logger error get token %s', util.inspect(err));
                            cb(err);
                        } else {
                            if(res && res.length > 0) cb(null, res[0][0].retToken);
                            else cb(new Error('no result returned'));
                        }
                    })
                }
            }
        ], function(err, result) {
            if(err) {
                logger.error('login %s', util.inspect(err));
                callback(err);
            }
            else {
                callback(null, result);
            }
        });
    }
};

exports.userbytoken = function(token, callback) {
    if(!token) {
        logger.error('userbytoken token undefined');
        callback(new Error('token undefined'));
    } else {
        processQuery('userbytoken', sqlsConfig.getUserByToken, [token, config.user.tokenExpiredIn], function(err, results) {
            if(err) {
                logger.error('userbytoken %s', util.inspect(err));
                callback(err);
            }
            else if(results && results.length > 0) {
               callback(null, {user_id: results[0].user_id, entity_type: results[0].entity_type, entity_id : results[0].entity_id,  result:'OK'});
            } else {
                callback(null, {user_id:-1, result : 'not found'});
            }
        })
    }
};

exports.getuserstatus = function(user_id, callback) {
    if(!user_id) {
        logger.error('getuserstatus user_id undefined');
        callback('user_id undefined');
    } else {
        processQuery('getuserstatus', sqlsConfig.getUserStatus, [user_id, config.user.statusExpiredIn], function(err, results) {
            if(err) {
                logger.error('getuserstatus %s', util.inspect(err));
                callback(err);
            } else {
                if(results && results.length > 0) callback(null, results[0].status);
                else callback(null, null);
            }
        });
    }
};

exports.updateuserstatus = function(user_id, callback) {
    if(!user_id) {
        logger.error('updateuserstatus user_id undefined');
        callback('user_id undefined');
    } else {
        processQuery('updateuserstatus', sqlsConfig.updateUserStatus, [user_id], function(err, results) {
            if(err) {
                logger.error('updateuserstatus %s', util.inspect(err));
                callback(err);
            } else {
                callback(null, results);
            }
        });
    }
};

exports.doctorappoptions = function(doctor_id, callback) {
    if(!doctor_id) {
        logger.error('doctorappoptions doctor_id undefined');
        callback('doctor_id undefined');
    } else {
        var sql = sqlsConfig.getDoctorAppOptions.replace(/:doctorId/g, doctor_id);
        processQuery('doctorappoptions', sql, function(err, results) {
            if(err) {
                logger.error('doctorappoptions %s',  util.inspect(err));
                callback(err);
            } else {
                callback(null, results);
            }
        });
    }
};

exports.doctorsearch = function(terms, callback)   {
    if(!terms || terms.length == 0) {
        logger.error('doctorsearch terms undefined');
        callback('terms undefined');
    } else {
        var dynamicSql = '';
        for(var i = 0; i < terms.length; i++) {
            if(terms[i]) {
                if(i > 0) dynamicSql += ' or ';
                dynamicSql += sqlsConfig.searchDynamicPart.replace(/:term/g, terms[i]);
            }
        }

        var sql = sqlsConfig.searchMainPart.replace(/:dynamic/g, dynamicSql);
        processQuery('doctorsearch', sql, function(err, results){
            if(err) {
                logger.error('doctorsearch %s',  util.inspect(err));
                callback(err);
            } else {
                callback(null, results);
            }
        });
    }
}

exports.getdoctor = function(doctor_id, callback) {
    if(!doctor_id) {
        logger.error('getdoctor doctor_id undefined');
        callback(new Error("doctor_id undefined"));
    } else {
        processQuery("getdoctor", sqlsConfig.getDoctor, [doctor_id], callback);
    }
}

exports.getpatient = function(patient_id, callback) {
    if(!patient_id) {
        logger.error('getpatient patient_id undefined');
        callback(new Error("patient_id undefined"));
    } else {
        processQuery("getpatient", sqlsConfig.getPatient, [patient_id], callback);
    }
}

exports.getpatientappointments = function(patient_id, callback) {
    if(!patient_id) {
        logger.error('getpatientappointments patient_id undefined');
        callback(new Error("doctor_id undefined"));
    } else {
        processQuery("getpatientappointments", sqlsConfig.getPatientAppointments, [patient_id], callback);
    }
}

exports.getappointmentinfo = function(app_id, callback) {
    if(!app_id) {
        logger.error('getappointmentinfo app_id undefined');
        callback(new Error("app_id undefined"));
    } else {
        processQuery("getappointmentinfo", sqlsConfig.appointmentInfo, [app_id], callback);
    }
}

exports.getpatientapphistory = function(patient_id, page, size, callback) {
    var pg = page? page : 0;
    var sz = size? size : 10;
    if(!patient_id) {
        logger.error('getpatientapphistory patient_id undefined');
        callback(new Error("patient_id undefined"));
    } else {
        processQuery("getpatientapphistory", sqlsConfig.patientAppointmentHistory, [patient_id, pg, sz], callback);
    }
}

exports.getpatientpersonalinfo = function(patient_id, callback) {
    if(!patient_id) {
        logger.error('getpatientpersonalinfo patient_id undefined');
        callback(new Error("patient_id undefined"));
    } else {
        processQuery("getpatientpersonalinfo", sqlsConfig.patientPersonalInfo, [patient_id], callback);
    }
}

exports.updatepatientinfo = function(patient_info, callback) {
    if(!patient_info) {
        logger.error('updatepatientinfo patient_info undefined');
        callback(new Error("patient_info undefined"));
    } else {
        if (patient_info.patient_id) {
            var allowed_fields = ['name', 'birthday', 'marital_status', 'occupation', 'address', 'picture_url', 'balance', "patient_id"];
            var object_keys = Object.keys(patient_info);
            for (var i = 0; i < object_keys.length; i++) {
                if (allowed_fields.indexOf(object_keys[i]) < 0) {
                    logger.error(util.format('updatepatientinfo %s not allowed field', object_keys[i]));
                    callback(new Error(util.format('%s not allowed field', object_keys[i])));
                    return;
                }
            }

            var updateStatement = '';
            for (var i = 0; i < object_keys.length; i++) {
                if (object_keys[i] != 'patient_id') {
                    if (i > 0) {
                        updateStatement += util.format(", %s='%s'", object_keys[i], patient_info[object_keys[i]].toString().replace(/'/g, "''"));
                    } else {
                        updateStatement += util.format(" %s='%s'", object_keys[i], patient_info[object_keys[i]].toString().replace(/'/g, "''"));
                    }
                }
            }

            var sql = sqlsConfig.updatePatientInfo.replace(/:updateStatement/, updateStatement).replace(/:id/, patient_info.patient_id);

            processQuery('updatepatientinfo', sql, function(err, results) {
                if(err) callback(err);
                else {
                    callback(null,results);
                }
            })

        } else {
            logger.error('updatepatientinfo patient_id field undefined');
            callback(new Error('patient_id field undefined'));
        }
    }
}

<commit_msg>Fix logging bug<commit_after>/**
 * Created by ozlevka on 3/21/14.
 */
require('date-utils');
var mysql = require('mysql');
var config = require('../config/config');
var fs = require('fs');
var async = require('async');
var winston = require('winston');
var util = require('util');
var sqlsConfig = require('../config/sqls');

var logger = new (winston.Logger)({
    transports : [
        new (winston.transports.Console)(config.logger.console),
        new (winston.transports.File)(config.logger.file)
    ]
});

var pool = mysql.createPool(config.mysql);

exports = module.exports;

/*
    Process single query
*/
function processQuery(endpoint, sql, params, callback) {
    if(typeof params === 'function') {
        callback = params
        params  = null;
    }
    pool.getConnection(function(err, connection){
        if(err) {
            logger.log('error', util.format('%s getConnection failed', endpoint) , util.inspect(err));
            callback(err);
        } else {
            var queryCb = function(err, results){
                if(err) {
                    logger.log('error', '%s query failed for params=%s', util.inspect(err), util.inspect(params));
                    callback(err);
                } else {
                    logger.debug(util.format('%s suces process sql= %s, params=  %s', endpoint, sql, util.inspect(params)));
                    callback(null, results);
                }
                connection.release();
            };
            if(!params) {
                connection.query(sql,queryCb);
            } else {
                connection.query(sql, params, queryCb);
            }
        }
    })
}

function createQueryFromObject(obj) {
    var columns = Object.keys(obj);
    var values = [];

    for(var i = 0; i < columns.length; i++) {
        values.push(obj[columns[i]]);
    }

    return {
        columns : columns,
        values : values
    }
}

/*
* Process number sqls in transation;
* */
function processTransaction(sqls, callback) {
    pool.getConnection(function(err, connection){
        if(err) {
            logger.log('error', 'processTransaction getConnection', util.inspect(err));
            callback(err);
        }
        else {
            connection.beginTransaction(function(err){
                if(err) {
                    logger.log('processTransaction beginTransaction', util.inspect(err));
                    callback(err);
                }
                else {
                    async.map(sqls, function(sql, callback) {
                        logger.debug(util.format('process query %s', sql));
                        connection.query(sql,function(err, res) {
                            if(err) {
                                logger.log('error', util.format('processTransaction executeQuery: %s', sql), util.inspect(err));
                                callback(err);
                            } else {
                                logger.debug(util.format('query %s success', sql));
                                callback(null, res);
                            }
                        });
                    }, function(err, results){
                        if(err) {
                            connection.rollback(function() {
                                logger.log('error','processTransaction connectionRolled back', util.inspect(err));
                                callback(err);
                            })
                        }
                        else {
                            connection.commit(function(err) {
                                if(err) {
                                    logger.log('error', 'processTransaction commit failed', util.inspect(err));
                                    connection.rollback(function(){
                                        callback(err);
                                    });
                                } else {
                                    logger.debug('processTransaction success');
                                    callback(null,results)
                                }
                            });
                        }
                    });
                }
            });
        }
    });
};

function parseTimeFromString(time) {
    if(typeof time == 'string') {
        var splittedTime = time.split(':');
        for(var i = 0; i < splittedTime.length; i++) {
            splittedTime[i] = splittedTime[i] * 1;
        }

        return splittedTime[0] * 60 + splittedTime[1];
    }

    return 0;
}

exports.releasePool = function() {
    pool.end(function(err){
        if(err) {
            logger.log('error', 'releasePool', err);
        }
    })
}

exports.login = function(user_name, password, callback) {
    if(!user_name || !password) {
        logger.error('login user_name or password undefined');
        callback(new Error("user_name or password undefined"));
    } else {
        async.waterfall([
            function(cb) {
                processQuery('login', sqlsConfig.loginGetUser,[user_name, password], function(e, results) {
                    if(e) cb(e);
                    else {
                        cb(null,results);
                    }
                })
            },
            function(user_res, cb) {
                if(!user_res || user_res.length == 0) {
                    logger.error('login user %s password %s not found', user_name, password);
                    cb(new Error('user not found'));
                } else {
                    cb(null,user_res[0].id);
                }
            },
            function(user_id, cb) {
                    if(!user_id) cb(new Error('strange ' + user_id + ' user_id undefined'));
                else {
                    processQuery('login', sqlsConfig.getToken,[user_id, config.user.tokenExpiredIn], function(err, res){
                        if(err) {
                            logger.error('logger error get token %s', util.inspect(err));
                            cb(err);
                        } else {
                            if(res && res.length > 0) cb(null, res[0][0].retToken);
                            else cb(new Error('no result returned'));
                        }
                    })
                }
            }
        ], function(err, result) {
            if(err) {
                logger.error('login %s', util.inspect(err));
                callback(err);
            }
            else {
                callback(null, result);
            }
        });
    }
};

exports.userbytoken = function(token, callback) {
    if(!token) {
        logger.error('userbytoken token undefined');
        callback(new Error('token undefined'));
    } else {
        processQuery('userbytoken', sqlsConfig.getUserByToken, [token, config.user.tokenExpiredIn], function(err, results) {
            if(err) {
                logger.error('userbytoken %s', util.inspect(err));
                callback(err);
            }
            else if(results && results.length > 0) {
               callback(null, {user_id: results[0].user_id, entity_type: results[0].entity_type, entity_id : results[0].entity_id,  result:'OK'});
            } else {
                callback(null, {user_id:-1, result : 'not found'});
            }
        })
    }
};

exports.getuserstatus = function(user_id, callback) {
    if(!user_id) {
        logger.error('getuserstatus user_id undefined');
        callback('user_id undefined');
    } else {
        processQuery('getuserstatus', sqlsConfig.getUserStatus, [user_id, config.user.statusExpiredIn], function(err, results) {
            if(err) {
                logger.error('getuserstatus %s', util.inspect(err));
                callback(err);
            } else {
                if(results && results.length > 0) callback(null, results[0].status);
                else callback(null, null);
            }
        });
    }
};

exports.updateuserstatus = function(user_id, callback) {
    if(!user_id) {
        logger.error('updateuserstatus user_id undefined');
        callback('user_id undefined');
    } else {
        processQuery('updateuserstatus', sqlsConfig.updateUserStatus, [user_id], function(err, results) {
            if(err) {
                logger.error('updateuserstatus %s', util.inspect(err));
                callback(err);
            } else {
                callback(null, results);
            }
        });
    }
};

exports.doctorappoptions = function(doctor_id, callback) {
    if(!doctor_id) {
        logger.error('doctorappoptions doctor_id undefined');
        callback('doctor_id undefined');
    } else {
        var sql = sqlsConfig.getDoctorAppOptions.replace(/:doctorId/g, doctor_id);
        processQuery('doctorappoptions', sql, function(err, results) {
            if(err) {
                logger.error('doctorappoptions %s',  util.inspect(err));
                callback(err);
            } else {
                callback(null, results);
            }
        });
    }
};

exports.doctorsearch = function(terms, callback)   {
    if(!terms || terms.length == 0) {
        logger.error('doctorsearch terms undefined');
        callback('terms undefined');
    } else {
        var dynamicSql = '';
        for(var i = 0; i < terms.length; i++) {
            if(terms[i]) {
                if(i > 0) dynamicSql += ' or ';
                dynamicSql += sqlsConfig.searchDynamicPart.replace(/:term/g, terms[i]);
            }
        }

        var sql = sqlsConfig.searchMainPart.replace(/:dynamic/g, dynamicSql);
        processQuery('doctorsearch', sql, function(err, results){
            if(err) {
                logger.error('doctorsearch %s',  util.inspect(err));
                callback(err);
            } else {
                callback(null, results);
            }
        });
    }
}

exports.getdoctor = function(doctor_id, callback) {
    if(!doctor_id) {
        logger.error('getdoctor doctor_id undefined');
        callback(new Error("doctor_id undefined"));
    } else {
        processQuery("getdoctor", sqlsConfig.getDoctor, [doctor_id], callback);
    }
}

exports.getpatient = function(patient_id, callback) {
    if(!patient_id) {
        logger.error('getpatient patient_id undefined');
        callback(new Error("patient_id undefined"));
    } else {
        processQuery("getpatient", sqlsConfig.getPatient, [patient_id], callback);
    }
}

exports.getpatientappointments = function(patient_id, callback) {
    if(!patient_id) {
        logger.error('getpatientappointments patient_id undefined');
        callback(new Error("doctor_id undefined"));
    } else {
        processQuery("getpatientappointments", sqlsConfig.getPatientAppointments, [patient_id], callback);
    }
}

exports.getappointmentinfo = function(app_id, callback) {
    if(!app_id) {
        logger.error('getappointmentinfo app_id undefined');
        callback(new Error("app_id undefined"));
    } else {
        processQuery("getappointmentinfo", sqlsConfig.appointmentInfo, [app_id], callback);
    }
}

exports.getpatientapphistory = function(patient_id, page, size, callback) {
    var pg = page? page : 0;
    var sz = size? size : 10;
    if(!patient_id) {
        logger.error('getpatientapphistory patient_id undefined');
        callback(new Error("patient_id undefined"));
    } else {
        processQuery("getpatientapphistory", sqlsConfig.patientAppointmentHistory, [patient_id, pg, sz], callback);
    }
}

exports.getpatientpersonalinfo = function(patient_id, callback) {
    if(!patient_id) {
        logger.error('getpatientpersonalinfo patient_id undefined');
        callback(new Error("patient_id undefined"));
    } else {
        processQuery("getpatientpersonalinfo", sqlsConfig.patientPersonalInfo, [patient_id], callback);
    }
}

exports.updatepatientinfo = function(patient_info, callback) {
    if(!patient_info) {
        logger.error('updatepatientinfo patient_info undefined');
        callback(new Error("patient_info undefined"));
    } else {
        if (patient_info.patient_id) {
            var allowed_fields = ['name', 'birthday', 'marital_status', 'occupation', 'address', 'picture_url', 'balance', "patient_id"];
            var object_keys = Object.keys(patient_info);
            for (var i = 0; i < object_keys.length; i++) {
                if (allowed_fields.indexOf(object_keys[i]) < 0) {
                    logger.error(util.format('updatepatientinfo %s not allowed field', object_keys[i]));
                    callback(new Error(util.format('%s not allowed field', object_keys[i])));
                    return;
                }
            }

            var updateStatement = '';
            for (var i = 0; i < object_keys.length; i++) {
                if (object_keys[i] != 'patient_id') {
                    if (i > 0) {
                        updateStatement += util.format(", %s='%s'", object_keys[i], patient_info[object_keys[i]].toString().replace(/'/g, "''"));
                    } else {
                        updateStatement += util.format(" %s='%s'", object_keys[i], patient_info[object_keys[i]].toString().replace(/'/g, "''"));
                    }
                }
            }

            var sql = sqlsConfig.updatePatientInfo.replace(/:updateStatement/, updateStatement).replace(/:id/, patient_info.patient_id);

            processQuery('updatepatientinfo', sql, function(err, results) {
                if(err) callback(err);
                else {
                    callback(null,results);
                }
            })

        } else {
            logger.error('updatepatientinfo patient_id field undefined');
            callback(new Error('patient_id field undefined'));
        }
    }
}

<|endoftext|>


<commit_before>/*

dev_mode_app.js
===============

This is the entry point for development(TDD) mode.

*/

var EventEmitter = require('events').EventEmitter
var Backbone = require('backbone')
var async = require('async')
var Server = require('../server')
var fs = require('fs')
var log = require('npmlog')
var AppView = require('./ui/appview')
var HookRunner = require('../hook_runner')
var child_process = require('child_process')
var BrowserRunner = require('../browser_runner')
var Path = require('path')
var yaml = require('js-yaml')
var fireworm = require('fireworm')
var Config = require('../config')
var browser_launcher = require('../browser_launcher')
var Launcher = require('../launcher')
var StyledString = require('styled_string')

function App(config, finalizer){
  var self = this

  this.config = config
  this.runners = new Backbone.Collection

  this
    .on('all-test-results', function () {
      var allRunnersComplete = self.runners.all(function (runner) {
        var results = runner.get('results')
        return results && !!results.get('all')
      })
      if (allRunnersComplete) {
        self.emit('all-runners-complete')
      }
    })

  this.exited = false
  this.paused = false
  this.finalizer = finalizer || process.exit
  this.fileWatcher = fireworm('./', {
    ignoreInitial: true
  })
  var onFileChanged = this.onFileChanged.bind(this)
  this.fileWatcher.on('change', onFileChanged)
  this.fileWatcher.on('add', onFileChanged)
  this.fileWatcher.on('remove', onFileChanged)
  this.fileWatcher.on('emfile', this.onEMFILE.bind(this))

  // a list of connected browser clients
  this.runners.on('remove', function(runner){
    runner.unbind()
  })

  this.configureView()

  this.configure(function(){
    this.server = new Server(config)
    this.server.on('server-start', this.initView.bind(this))
    this.server.on('file-requested', this.onFileRequested.bind(this))
    this.server.on('browser-login', this.onBrowserLogin.bind(this))
    this.server.on('server-error', this.onServerError.bind(this))
    this.server.start()
  })

  process.on('uncaughtException', function(err){
    console.error(err)
    self.quit(1, err)
  })

}

App.prototype = {
  __proto__: EventEmitter.prototype,
  start: function(){},
  initView: function(){
    var self = this
    var view = this.view
    if (this.view.on)
      this.view.on('inputChar', this.onInputChar.bind(this))

    this.on('all-runners-complete', function(){
      self.runPostprocessors()
    })

    self.startOnStartHook(function(err){
      if (err){
        var titleText = 'Error running on_start hook'
        var title = StyledString(titleText + '\n ').foreground('red')
        var errMsgs = StyledString('\n' + err.name).foreground('white')
          .concat(StyledString('\n' + err.stdout).foreground('yellow'))
          .concat(StyledString('\n' + err.stderr).foreground('red'))
        view.setErrorPopupMessage(title.concat(errMsgs))
        log.log('warn', titleText, {
          name: err.name,
          stdout: err.stdout,
          stderr: err.stderr
        })
        return
      } else {
        self.startTests(function(){
          self.initLaunchers()
        })
      }
    })
  },
  initLaunchers: function(){
    var config = this.config
    var launch_in_dev = config.get('launch_in_dev')
    var self = this

    config.getLaunchers(function(launchers){
      self.launchers = launchers
      launchers.forEach(function(launcher){
        log.info('Launching ' + launcher.name)
        launcher.start()
        if (launcher.runner){
          self.runners.push(launcher.runner)

        }
      })
    })
  },
  configureView: function() {
    var self = this
    this.view = new AppView({
      app: this
    })
    this.view.on('ctrl-c', function(){
      self.quit()
    })
  },
  configure: function(cb){
    var self = this
    var fileWatcher = self.fileWatcher
    var config = self.config
    config.read(function(){
      var watchFiles = config.get('watch_files')
      fileWatcher.clear()
      var confFile = config.get('file')
      if (confFile){
        fileWatcher.add(confFile)
      }
      if (config.isCwdMode()){
        fileWatcher.add('*.js')
      }
      if (watchFiles) {
        fileWatcher.add(watchFiles)
      }
      var srcFiles = config.get('src_files') || '*.js'
      fileWatcher.add(srcFiles)
      var ignoreFiles = config.get('src_files_ignore')
      if (ignoreFiles){
        fileWatcher.ignore(ignoreFiles)
      }
      if (cb) cb.call(self)
    })
  },
  onFileRequested: function(filepath){
    if (!this.config.get('serve_files')){
      this.fileWatcher.add(filepath)
    }
  },
  onFileChanged: function(filepath){
    log.info(filepath + ' changed ('+(this.disableFileWatch ? 'disabled' : 'enabled')+').')
    if (this.disableFileWatch || this.paused) return
    var configFile = this.config.get('file')
    if ((configFile && filepath === Path.resolve(configFile)) ||
      (this.config.isCwdMode() && filepath === process.cwd())){
      // config changed
      this.configure(this.startTests.bind(this))
    }else{
      this.runHook('on_change', {file: filepath}, this.startTests.bind(this))
    }
  },
  onEMFILE: function(){
    var view = this.view
    var text = [
      'The file watcher received a EMFILE system error, which means that ',
      'it has hit the maximum number of files that can be open at a time. ',
      'Luckily, you can increase this limit as a workaround. See the directions below \n \n',
      'Linux: http://stackoverflow.com/a/34645/5304\n',
      'Mac OS: http://serverfault.com/a/15575/47234'
    ].join('')
    view.setErrorPopupMessage(StyledString(text + '\n ').foreground('megenta'))
  },
  onServerError: function(err){
    this.quit(1, err)
  },
  onGeneralWatcherError: function(message){
    log.error('Error from fireworm: ' + message)
  },
  onBrowserLogin: function(browserName, id, client){
    this.connectBrowser(browserName, id, client)
  },
  quit: function(code, err){
    if (this.exited) return

    var self = this
    this.emit('exit')
    this.cleanUpLaunchers(function(){
      self.runExitHook(function(){
        if (self.view) self.view.cleanup(die)
        else die()
        function die(){
          if (err) console.error(err.stack)
          self.finalizer(code)
          self.exited = true
        }
      })
    })
  },
  onInputChar: function(chr, i) {
    var self = this
    if (chr === 'q') {
      log.info('Got keyboard Quit command')
      this.quit()
    }
    else if (i === 13){ // ENTER
      log.info('Got keyboard Start Tests command')
      this.startTests()
    }
    else if (chr === 'p') {
      this.paused = !this.paused
      this.view.renderBottom()
    }
  },
  startTests: function(callback){
    if (this.paused) {
      return
    }
    try{
      var view = this.view
      var runners = this.runners
      this.runPreprocessors(function(err){
        if (err){
          var titleText = 'Error running before_tests hook'
          var title = StyledString(titleText + '\n ').foreground('red')
          var errMsgs = StyledString('\n' + err.name).foreground('white')
            .concat(StyledString('\n' + err.stdout).foreground('yellow'))
            .concat(StyledString('\n' + err.stderr).foreground('red'))
          view.setErrorPopupMessage(title.concat(errMsgs))
          log.log('warn', titleText, {
            name: err.name,
            stdout: err.stdout,
            stderr: err.stderr
          })
          return
        }else{
          view.clearErrorPopupMessage()
          runners.forEach(function(runner){
            runner.startTests()
          })
          if (callback) callback()
        }
      })
    }catch(e){
      log.info(e.message)
      log.info(e.stack)
    }
  },
  runPreprocessors: function(callback){
    this.runHook('before_tests', callback)
  },
  runPostprocessors: function(callback){
    this.runHook('after_tests', callback)
  },
  startOnStartHook: function(callback){
    this.onStartProcess = new HookRunner(this.config)
    this.onStartProcess.run('on_start', [], callback)
  },
  runExitHook: function (callback) {
    if(this.onStartProcess) {
      this.onStartProcess.stop()
    }
    this.runHook('on_exit', callback)
  },
  runHook: function(/*hook, data..., callback*/){
    var hook = arguments[0]
    var callback = arguments[arguments.length-1]
    var data = arguments.length > 2 ? arguments[1] : {}
    var runner = new HookRunner(this.config)
    var self = this
    log.info("Hook "+hook+" started")
    this.disableFileWatch = true
    runner.run(hook, data, function(err){
      log.info("Hook "+hook+" finished")
      self.disableFileWatch = false
      if (callback) { callback(err) }
    })
  },
  removeBrowser: function(browser){
    this.runners.remove(browser)
  },
  connectBrowser: function(browserName, id, client){
    var existing = this.runners.find(function(runner){
      return runner.pending && runner.get('name') === browserName
    })
    if (existing){
      clearTimeout(existing.pending)
      existing.set('socket', client)
      return existing
    }else{
      var browser = new BrowserRunner({
        name: browserName,
        socket: client
      })
      var self = this
      browser.on('disconnect', function(){
        browser.pending = setTimeout(function(){
          self.removeBrowser(browser)
        }, 1000)
      })
      browser.on('all-test-results', function(results, browser){
        self.emit('all-test-results', results, browser)
      })
      browser.on('top-level-error', function(msg, url, line){
        self.emit('top-level-error', msg, url, line)
      })
      this.runners.push(browser)
      return browser
    }
  },
  cleanUpLaunchers: function(callback){
    if (!this.launchers){
      if (callback) callback()
      return
    }
    async.forEach(this.launchers, function(launcher, done){
      if (launcher && launcher.process){
        launcher.kill('SIGTERM', done)
      }else{
        done()
      }
    }, callback)
  }
}

module.exports = App
<commit_msg>Do not suppress errors in tests<commit_after>/*

dev_mode_app.js
===============

This is the entry point for development(TDD) mode.

*/

var EventEmitter = require('events').EventEmitter
var Backbone = require('backbone')
var async = require('async')
var Server = require('../server')
var fs = require('fs')
var log = require('npmlog')
var AppView = require('./ui/appview')
var HookRunner = require('../hook_runner')
var child_process = require('child_process')
var BrowserRunner = require('../browser_runner')
var Path = require('path')
var yaml = require('js-yaml')
var fireworm = require('fireworm')
var Config = require('../config')
var browser_launcher = require('../browser_launcher')
var Launcher = require('../launcher')
var StyledString = require('styled_string')

function App(config, finalizer){
  var self = this

  this.config = config
  this.runners = new Backbone.Collection

  this
    .on('all-test-results', function () {
      var allRunnersComplete = self.runners.all(function (runner) {
        var results = runner.get('results')
        return results && !!results.get('all')
      })
      if (allRunnersComplete) {
        self.emit('all-runners-complete')
      }
    })

  this.exited = false
  this.paused = false
  this.finalizer = finalizer || process.exit
  this.fileWatcher = fireworm('./', {
    ignoreInitial: true
  })
  var onFileChanged = this.onFileChanged.bind(this)
  this.fileWatcher.on('change', onFileChanged)
  this.fileWatcher.on('add', onFileChanged)
  this.fileWatcher.on('remove', onFileChanged)
  this.fileWatcher.on('emfile', this.onEMFILE.bind(this))

  // a list of connected browser clients
  this.runners.on('remove', function(runner){
    runner.unbind()
  })

  this.configureView()

  this.configure(function(){
    this.server = new Server(config)
    this.server.on('server-start', this.initView.bind(this))
    this.server.on('file-requested', this.onFileRequested.bind(this))
    this.server.on('browser-login', this.onBrowserLogin.bind(this))
    this.server.on('server-error', this.onServerError.bind(this))
    this.server.start()
  })

  process.on('uncaughtException', function(err){
    self.quit(1, err)
  })

}

App.prototype = {
  __proto__: EventEmitter.prototype,
  start: function(){},
  initView: function(){
    var self = this
    var view = this.view
    if (this.view.on)
      this.view.on('inputChar', this.onInputChar.bind(this))

    this.on('all-runners-complete', function(){
      self.runPostprocessors()
    })

    self.startOnStartHook(function(err){
      if (err){
        var titleText = 'Error running on_start hook'
        var title = StyledString(titleText + '\n ').foreground('red')
        var errMsgs = StyledString('\n' + err.name).foreground('white')
          .concat(StyledString('\n' + err.stdout).foreground('yellow'))
          .concat(StyledString('\n' + err.stderr).foreground('red'))
        view.setErrorPopupMessage(title.concat(errMsgs))
        log.log('warn', titleText, {
          name: err.name,
          stdout: err.stdout,
          stderr: err.stderr
        })
        return
      } else {
        self.startTests(function(){
          self.initLaunchers()
        })
      }
    })
  },
  initLaunchers: function(){
    var config = this.config
    var launch_in_dev = config.get('launch_in_dev')
    var self = this

    config.getLaunchers(function(launchers){
      self.launchers = launchers
      launchers.forEach(function(launcher){
        log.info('Launching ' + launcher.name)
        launcher.start()
        if (launcher.runner){
          self.runners.push(launcher.runner)

        }
      })
    })
  },
  configureView: function() {
    var self = this
    this.view = new AppView({
      app: this
    })
    this.view.on('ctrl-c', function(){
      self.quit()
    })
  },
  configure: function(cb){
    var self = this
    var fileWatcher = self.fileWatcher
    var config = self.config
    config.read(function(){
      var watchFiles = config.get('watch_files')
      fileWatcher.clear()
      var confFile = config.get('file')
      if (confFile){
        fileWatcher.add(confFile)
      }
      if (config.isCwdMode()){
        fileWatcher.add('*.js')
      }
      if (watchFiles) {
        fileWatcher.add(watchFiles)
      }
      var srcFiles = config.get('src_files') || '*.js'
      fileWatcher.add(srcFiles)
      var ignoreFiles = config.get('src_files_ignore')
      if (ignoreFiles){
        fileWatcher.ignore(ignoreFiles)
      }
      if (cb) cb.call(self)
    })
  },
  onFileRequested: function(filepath){
    if (!this.config.get('serve_files')){
      this.fileWatcher.add(filepath)
    }
  },
  onFileChanged: function(filepath){
    log.info(filepath + ' changed ('+(this.disableFileWatch ? 'disabled' : 'enabled')+').')
    if (this.disableFileWatch || this.paused) return
    var configFile = this.config.get('file')
    if ((configFile && filepath === Path.resolve(configFile)) ||
      (this.config.isCwdMode() && filepath === process.cwd())){
      // config changed
      this.configure(this.startTests.bind(this))
    }else{
      this.runHook('on_change', {file: filepath}, this.startTests.bind(this))
    }
  },
  onEMFILE: function(){
    var view = this.view
    var text = [
      'The file watcher received a EMFILE system error, which means that ',
      'it has hit the maximum number of files that can be open at a time. ',
      'Luckily, you can increase this limit as a workaround. See the directions below \n \n',
      'Linux: http://stackoverflow.com/a/34645/5304\n',
      'Mac OS: http://serverfault.com/a/15575/47234'
    ].join('')
    view.setErrorPopupMessage(StyledString(text + '\n ').foreground('megenta'))
  },
  onServerError: function(err){
    this.quit(1, err)
  },
  onGeneralWatcherError: function(message){
    log.error('Error from fireworm: ' + message)
  },
  onBrowserLogin: function(browserName, id, client){
    this.connectBrowser(browserName, id, client)
  },
  quit: function(code, err){
    if (this.exited) return

    var self = this
    this.emit('exit')
    this.cleanUpLaunchers(function(){
      self.runExitHook(function(){
        if (self.view && self.view.cleanup) {
          self.view.cleanup(die)
        }
        else die()
        function die(){
          if (err) console.error(err.stack)
          self.finalizer(code)
          self.exited = true
        }
      })
    })
  },
  onInputChar: function(chr, i) {
    var self = this
    if (chr === 'q') {
      log.info('Got keyboard Quit command')
      this.quit()
    }
    else if (i === 13){ // ENTER
      log.info('Got keyboard Start Tests command')
      this.startTests()
    }
    else if (chr === 'p') {
      this.paused = !this.paused
      this.view.renderBottom()
    }
  },
  startTests: function(callback){
    if (this.paused) {
      return
    }
    try{
      var view = this.view
      var runners = this.runners
      this.runPreprocessors(function(err){
        if (err){
          var titleText = 'Error running before_tests hook'
          var title = StyledString(titleText + '\n ').foreground('red')
          var errMsgs = StyledString('\n' + err.name).foreground('white')
            .concat(StyledString('\n' + err.stdout).foreground('yellow'))
            .concat(StyledString('\n' + err.stderr).foreground('red'))
          view.setErrorPopupMessage(title.concat(errMsgs))
          log.log('warn', titleText, {
            name: err.name,
            stdout: err.stdout,
            stderr: err.stderr
          })
          return
        }else{
          view.clearErrorPopupMessage()
          runners.forEach(function(runner){
            runner.startTests()
          })
          if (callback) callback()
        }
      })
    }catch(e){
      log.info(e.message)
      log.info(e.stack)
    }
  },
  runPreprocessors: function(callback){
    this.runHook('before_tests', callback)
  },
  runPostprocessors: function(callback){
    this.runHook('after_tests', callback)
  },
  startOnStartHook: function(callback){
    this.onStartProcess = new HookRunner(this.config)
    this.onStartProcess.run('on_start', [], callback)
  },
  runExitHook: function (callback) {
    if(this.onStartProcess) {
      this.onStartProcess.stop()
    }
    this.runHook('on_exit', callback)
  },
  runHook: function(/*hook, data..., callback*/){
    var hook = arguments[0]
    var callback = arguments[arguments.length-1]
    var data = arguments.length > 2 ? arguments[1] : {}
    var runner = new HookRunner(this.config)
    var self = this
    log.info("Hook "+hook+" started")
    this.disableFileWatch = true
    runner.run(hook, data, function(err){
      log.info("Hook "+hook+" finished")
      self.disableFileWatch = false
      if (callback) { callback(err) }
    })
  },
  removeBrowser: function(browser){
    this.runners.remove(browser)
  },
  connectBrowser: function(browserName, id, client){
    var existing = this.runners.find(function(runner){
      return runner.pending && runner.get('name') === browserName
    })
    if (existing){
      clearTimeout(existing.pending)
      existing.set('socket', client)
      return existing
    }else{
      var browser = new BrowserRunner({
        name: browserName,
        socket: client
      })
      var self = this
      browser.on('disconnect', function(){
        browser.pending = setTimeout(function(){
          self.removeBrowser(browser)
        }, 1000)
      })
      browser.on('all-test-results', function(results, browser){
        self.emit('all-test-results', results, browser)
      })
      browser.on('top-level-error', function(msg, url, line){
        self.emit('top-level-error', msg, url, line)
      })
      this.runners.push(browser)
      return browser
    }
  },
  cleanUpLaunchers: function(callback){
    if (!this.launchers){
      if (callback) callback()
      return
    }
    async.forEach(this.launchers, function(launcher, done){
      if (launcher && launcher.process){
        launcher.kill('SIGTERM', done)
      }else{
        done()
      }
    }, callback)
  }
}

module.exports = App
<|endoftext|>


<commit_before>var _ = require('underscore'),
    moment = require('moment'),
    db = require('./db');

module.exports = {
    list: function(options, callback) {
        var docs = db.coll('documents'),
            page = options.page || 1,
            pageSize = options.pageSize || 25,
            query = {};

        if (!options.admin) {
            query.username = options.username;
        }
        if (options.approved !== undefined) {
            query.approved = options.approved;
        }
        if (options.country) {
            query.country = options.country;
        }

        docs.find({
            $orderby: {
                created_at: -1,
                year: -1,
                approved: 1
            },
            $query: query
        }).limit(pageSize).skip((page - 1) * pageSize).toArray(callback);
    },
    listAll: function(query, callback) {
        var docs = db.coll('documents');

        docs.find({
            $query: query
        }).toArray(callback);
    },
    drop: function() {
        var docs = db.coll('documents');

        docs.drop();
    },
    exists: function(options, callback) {
        module.exports.get(options, function(err, doc) {
            callback(err, !!doc);
        });
    },
    get: function(options, callback) {
        var docs = db.coll('documents'),
            id = module.exports.getId(options);

        docs.findOne({
            _id: id
        }, callback);
    },
    count: function(options, callback) {
        var docs = db.coll('documents'),
            query = {};

        if (options.$query) {
            query = options.$query;
        }

        docs.count(query, callback);
    },
    countUnapproved: function(options, callback) {
        options.$query = {
            approved: {
                $ne: true
            }
        };
        module.exports.count(options, callback);
    },
    insert: function(options, callback) {
        var docs = db.coll('documents'),
            data = options.data,
            now = new Date();

        data.username = options.username;
        data.created_at = data.last_modified = now;

        docs.insert(data, callback);
    },
    validateAndBuild: function(reqBody, assert) {
      assert('type', 'Type must be valid.').isIn([
          "Pre-Budget Statement",
          "Executive's Budget Proposal",
          "Enacted Budget",
          "Citizens Budget",
          "In-Year Report",
          "Mid-Year Review",
          "Year-End Report",
          "Audit Report"
      ]);

      assert('title', 'Title can\'t be empty').notEmpty();

      var dateFormat = 'DD MMM YYYY',
          datePublished = moment(reqBody.date_published, dateFormat),
          dateReceived = moment(reqBody.date_received, dateFormat);
      if (datePublished) {
        assert('date_published',
               'Date of publication should be in format 01 Jan 2014')
          .isValidDate();
      }
      if (dateReceived) {
        assert('date_received',
               'Date received should be in format 01 Jan 2014')
          .isValidDate();
      }

      return {
          type: reqBody.type,
          title: reqBody.title,
          available: reqBody.available === 'yes',
	  internal: reqBody.internal === 'yes',
          year: parseInt(reqBody.year, 10) || undefined,
          comments: reqBody.comments,
          location: reqBody.location || '',
          location_detail: reqBody.location_detail,
          url: reqBody.url,
          date_published: datePublished.format("DD MMM YYYY"),
          date_received: dateReceived.format("DD MMM YYYY"),
          softcopy: reqBody.softcopy === 'yes',
          scanned: reqBody.scanned === 'yes'
      };
    },
    getDocumentState: function(doc) {
      if (doc.available && !doc.internal && doc.date_published) {
         return 'available';
      }
      if (!doc.available && doc.internal && !doc.date_published) {
         return 'internal';
      }
      if (!doc.available && !doc.internal && doc.date_published) {
         return 'late';
      }

      return 'not produced';
    },
    addUpload: function(options, callback) {
        var docs = db.coll('documents');

        docs.update({
            _id: module.exports.getId(options)
        }, {
            $addToSet: {
                uploads: {
                    name: options.name,
                    filename: options.filename
                }
            }
        }, function(err, result) {
            callback(err, result);
        });
    },
    // hackery for mongodb's ObjectID function
    getId: function(options) {
        if (options.id && options.id.length === 24) {
            return db.ObjectID(options.id);
        } else {
            return options.id;
        }
    },
    update: function(options, callback) {
        var data = _.omit(options.data, 'username', 'created_at'), // in case they get passed
            docs = db.coll('documents'),
            now = new Date(),
            id = module.exports.getId(options);

        data.last_modified = now;

        if (options.resetApproved) {
            data.approved = undefined;
        }

        docs.update({
            _id: id
        }, {
            $set: data
        }, callback);
    }
};
<commit_msg>Move formatting of dates into the date processing (in case of null)<commit_after>var _ = require('underscore'),
    moment = require('moment'),
    db = require('./db');

module.exports = {
    list: function(options, callback) {
        var docs = db.coll('documents'),
            page = options.page || 1,
            pageSize = options.pageSize || 25,
            query = {};

        if (!options.admin) {
            query.username = options.username;
        }
        if (options.approved !== undefined) {
            query.approved = options.approved;
        }
        if (options.country) {
            query.country = options.country;
        }

        docs.find({
            $orderby: {
                created_at: -1,
                year: -1,
                approved: 1
            },
            $query: query
        }).limit(pageSize).skip((page - 1) * pageSize).toArray(callback);
    },
    listAll: function(query, callback) {
        var docs = db.coll('documents');

        docs.find({
            $query: query
        }).toArray(callback);
    },
    drop: function() {
        var docs = db.coll('documents');

        docs.drop();
    },
    exists: function(options, callback) {
        module.exports.get(options, function(err, doc) {
            callback(err, !!doc);
        });
    },
    get: function(options, callback) {
        var docs = db.coll('documents'),
            id = module.exports.getId(options);

        docs.findOne({
            _id: id
        }, callback);
    },
    count: function(options, callback) {
        var docs = db.coll('documents'),
            query = {};

        if (options.$query) {
            query = options.$query;
        }

        docs.count(query, callback);
    },
    countUnapproved: function(options, callback) {
        options.$query = {
            approved: {
                $ne: true
            }
        };
        module.exports.count(options, callback);
    },
    insert: function(options, callback) {
        var docs = db.coll('documents'),
            data = options.data,
            now = new Date();

        data.username = options.username;
        data.created_at = data.last_modified = now;

        docs.insert(data, callback);
    },
    validateAndBuild: function(reqBody, assert) {
      assert('type', 'Type must be valid.').isIn([
          "Pre-Budget Statement",
          "Executive's Budget Proposal",
          "Enacted Budget",
          "Citizens Budget",
          "In-Year Report",
          "Mid-Year Review",
          "Year-End Report",
          "Audit Report"
      ]);

      assert('title', 'Title can\'t be empty').notEmpty();

      var dateFormat = 'DD MMM YYYY',
          datePublished = moment(reqBody.date_published, dateFormat),
          dateReceived = moment(reqBody.date_received, dateFormat);
      if (datePublished) {
        assert('date_published',
               'Date of publication should be in format 01 Jan 2014')
          .isValidDate();
        datePublished = datePublished.format("DD MMM YYYY");
      }
      if (dateReceived) {
        assert('date_received',
               'Date received should be in format 01 Jan 2014')
          .isValidDate();
        dateReceived = dateReceived.format("DD MMM YYYY");
      }

      return {
          type: reqBody.type,
          title: reqBody.title,
          available: reqBody.available === 'yes',
	  internal: reqBody.internal === 'yes',
          year: parseInt(reqBody.year, 10) || undefined,
          comments: reqBody.comments,
          location: reqBody.location || '',
          location_detail: reqBody.location_detail,
          url: reqBody.url,
          date_published: datePublished,
          date_received: dateReceived,
          softcopy: reqBody.softcopy === 'yes',
          scanned: reqBody.scanned === 'yes'
      };
    },
    getDocumentState: function(doc) {
      if (doc.available && !doc.internal && doc.date_published) {
         return 'available';
      }
      if (!doc.available && doc.internal && !doc.date_published) {
         return 'internal';
      }
      if (!doc.available && !doc.internal && doc.date_published) {
         return 'late';
      }

      return 'not produced';
    },
    addUpload: function(options, callback) {
        var docs = db.coll('documents');

        docs.update({
            _id: module.exports.getId(options)
        }, {
            $addToSet: {
                uploads: {
                    name: options.name,
                    filename: options.filename
                }
            }
        }, function(err, result) {
            callback(err, result);
        });
    },
    // hackery for mongodb's ObjectID function
    getId: function(options) {
        if (options.id && options.id.length === 24) {
            return db.ObjectID(options.id);
        } else {
            return options.id;
        }
    },
    update: function(options, callback) {
        var data = _.omit(options.data, 'username', 'created_at'), // in case they get passed
            docs = db.coll('documents'),
            now = new Date(),
            id = module.exports.getId(options);

        data.last_modified = now;

        if (options.resetApproved) {
            data.approved = undefined;
        }

        docs.update({
            _id: id
        }, {
            $set: data
        }, callback);
    }
};
<|endoftext|>