<commit_before>var http = require('http'),
  moment = require('moment');

var leagueInfo = {
  leagueName: 'NFL',
  updateURL: 'http://www.nfl.com/liveupdate/scorestrip/ss.json',
  updateURL2: 'http://www.nfl.com/liveupdate/scorestrip/scorestrip.json'
};

var getGameArray = function(next) {
  var rawOuter = '';
  var outerRequest = http.get(leagueInfo.updateURL, function outHttpSetup(oRes) {
    oRes.on('data', function outHttpData(chunk) {
      rawOuter += chunk;
    });
    oRes.on('end', function outHttpEnd() {
      var rawInner = '';
      var innerRequest = http.get(leagueInfo.updateURL2, function inHttpSetup(iRes) {
        iRes.on('data', function inHttpData(chunk) {
          rawInner += chunk;
        });
        iRes.on('end', function inHttpEnd() {
          try {
            rawInner = rawInner.replace(/\,\,/g,',"",')
              .replace(/\,\,/g,',"",');
            innerArray = JSON.parse(rawInner).ss;
            var innerObj = { };
            for (var i = 0; i < innerArray.length; i++) {
              innerObj[innerArray[i][10]] = innerArray[i];
            }
            outerObj = JSON.parse(rawOuter);
            var gameArray = [];
            for (var i = 0; i < outerObj.gms.length; i++) {
              outerObj.gms[i].outer_w = outerObj.w;
              outerObj.gms[i].outer_t = outerObj.t;
              outerObj.gms[i].outer_y = outerObj.y;
              outerObj.gms[i].outer_gd = outerObj.gd;
              outerObj.gms[i].outer_bph = outerObj.bph;
              outerObj.gms[i].score_array = innerObj[outerObj.gms[i].gsis];
              gameArray.push(parseRawGame(outerObj.gms[i]));
            }
            next(null, gameArray);
          } catch(e) {
            console.log('NFL: parsing error');
            e.source = 'NFL';
            next(e);
          }
        });
      }).on('error', function inHttpEnd(e) {
        console.log('NFL inner http Error: ' + e.message);
      });
    });
  }).on('error', function outHttpErrir(e) {
    console.log('NFL outer http Error: ' + e.message);
  });
};

var parseRawGame = function(rawGame) {
  var game = { };
  var gameState = '';
  var timeString = rawGame.t;
  switch(rawGame.q) {
    case 'P':
      gameState = 'Scheduled';
      break;
    case 'H':
      gameState = 'Halftime';
      break;
    case 'F':
      gameState = 'Ended';
      break;
    case 'FO':
      gameState = 'Ended';
      break;
    case '5':
      gameState = 'Overtime';
      break;
    default:
      gameState = 'Progress';
      timeString = rawGame.score_array[3];
      break;
  }
  var endIndex = rawGame.eid.toString().length - 2;
  var dateStr = rawGame.eid.toString().substr(0,endIndex);
  var gameDate = moment(dateStr ,'YYYYMD').toDate();
  game.GameSymbol = rawGame.eid.toString();
  game.State = gameState;
  game.Date = gameDate;
  game.SeasonYear = rawGame.outer_y;
  game.SeasonType = rawGame.outer_t;
  game.SeasonWeek = rawGame.outer_w;
  game.Time = timeString;
  game.Quarter = rawGame.q;
  game.AwayTeamDisplayName = rawGame.v;
  game.AwayTeamName = rawGame.vnn;
  game.AwayScore = rawGame.vs;
  game.HomeTeamDisplayName = rawGame.h;
  game.HomeTeamName = rawGame.hnn;
  game.HomeScore = rawGame.hs;
  game.RawInstance = JSON.stringify(rawGame,null,2);
  return game;
};

var gameChanged = function(oldGame, newGame) {
  return (oldGame.GameSymbol === newGame.GameSymbol) &&
    ( (oldGame.State !== newGame.State) ||
      (oldGame.Quarter !== newGame.Quarter) ||
      (oldGame.AwayScore !== newGame.AwayScore) ||
      (oldGame.HomeScore !== newGame.HomeScore));
};

var gameChangeString = function(oldGame, newGame) {
  var dif = [];
  if (oldGame.State !== newGame.State)
    dif.push(oldGame.State + '-' + newGame.State);
  if (oldGame.Quarter !== newGame.Quarter)
    dif.push(oldGame.Quarter + '-' + newGame.Quarter);
  if (oldGame.AwayScore !== newGame.AwayScore)
    dif.push(oldGame.AwayScore + '-' + newGame.AwayScore);
  if (oldGame.HomeScore !== newGame.HomeScore)
    dif.push(oldGame.HomeScore + '-' + newGame.HomeScore);
  return dif.join(',');
};

var makeQuarterString = function(quarter) {
  switch (quarter) {
    case '1':
      return '1st';
    case '2':
      return '2nd';
    case '3':
      return '3rd';
    case '4':
      return '4th';
    case '5':
      return 'OT';
    default:
      return '';
  }
}

var gameChangeTweet = function(oldGame, newGame) {
  var tweet = {
    InstanceID: newGame.InstanceID,
    TweetString: ''
  };
  var scores = newGame.AwayTeamName + ' ' + newGame.AwayScore + ', ' +
    newGame.HomeTeamName + ' ' + newGame.HomeScore + ' ';
  if (oldGame.State !== newGame.State) {
    if (oldGame.State === 'Scheduled' && newGame.State === 'Progress') {
      tweet.TweetString = 'Start of game: ' +
        newGame.AwayTeamName + ' vs ' + newGame.HomeTeamName + ' ' +
        makeGameLink(newGame);
    }
   /* if (oldGame.State === 'Progress' && newGame.State === 'Halftime') {
      tweet.TweetString = 'Halftime. ' +
        scores + makeGameLink(newGame);
    }
    if (oldGame.State === 'Halftime' && newGame.State === 'Progress') {
      tweet.TweetString = 'Start of 3rd. ' +
        scores + makeGameLink(newGame);
    }
    if (oldGame.State === 'Progress' && newGame.State === 'Overtime') {
      tweet.TweetString = 'Headed to OT. ' +
        scores + makeGameLink(newGame);
    }*/
    if (newGame.State === 'Ended') {
      if (oldGame.State === 'Overtime') {
        tweet.TweetString = 'Final OT. ' +
          scores + makeGameLink(newGame);
      } else {
        tweet.TweetString = 'Final. ' +
          scores + makeGameLink(newGame);
      }
    }
  /*} else if (oldGame.Quarter !== newGame.Quarter) {
      tweet.TweetString = 'Start of ' + makeQuarterString(newGame.Quarter) + '. ' +
        scores + makeGameLink(newGame);*/
  } else if ((oldGame.Quarter === newGame.Quarter
    && oldGame.Time > newGame.Time) ||
    (oldGame.Quarter < newGame.Quarter)) {
    if (oldGame.AwayScore !== newGame.AwayScore) {
      tweet.TweetString = newGame.AwayTeamName + ' score. ' +
        scores + newGame.Time + ' ' +
        makeQuarterString(newGame.Quarter) + ' ' +
        makeGameLink(newGame);
    }
    if (oldGame.HomeScore !== newGame.HomeScore) {
      tweet.TweetString = newGame.HomeTeamName + ' score. ' +
        scores + newGame.Time + ' ' +
        makeQuarterString(newGame.Quarter) + ' ' +
        makeGameLink(newGame);
    }
  }
  return tweet;
};

var makeGameLink = function(game) {
  var link = 'http://www.nfl.com/gamecenter/' +
    game.GameSymbol +
    '/' +
    game.SeasonYear +
    '/' +
    game.SeasonType +
    game.SeasonWeek +
    '/' +
    game.AwayTeamName +
    '@' +
    game.HomeTeamName;
  return link;
};

var insertGameQuery = function(game) {
  var stmnt =
    'Insert into NFLGames(GameSymbol,Date,SeasonYear,SeasonType,\
      SeasonWeek,AwayTeamID,HomeTeamID)\
    Select\
      ?,?,?,?,?,away.TeamID,home.TeamID\
    from NFLTeams away\
      inner join NFLTeams home\
        on LOWER(REPLACE(home.Name,\' \',\'\')) like ?\
    where LOWER(REPLACE(away.Name,\' \',\'\')) like ?\
    and not exists\
      (Select 1 from NFLGames where GameSymbol like ?);';
  var params = [game.GameSymbol, game.Date, game.SeasonYear, game.SeasonType,
    game.SeasonWeek, game.HomeTeamName, game.AwayTeamName, game.GameSymbol];
  return {
    sql: stmnt,
    inserts: params
  };
};

var insertGameInstanceQuery = function(game) {
  var stmnt =
    'Insert into NFLGameInstances(GameID,StateID,Time,\
      Quarter,AwayScore,HomeScore,RawInstance)\
    Select\
      game.GameID,state.StateID,?,?,?,?,?\
    from NFLStates state\
      inner join NFLGames game\
        on game.GameSymbol like ?\
    where state.State = ?;';
  var params = [game.Time, game.Quarter, game.AwayScore,
    game.HomeScore, game.RawInstance, game.GameSymbol, game.State];
  return {
    sql: stmnt,
    inserts: params
  };
};

var lastGameInstanceQuery = function(game) {
  var stmnt =
    'Select\
      instance.InstanceID\
    , game.GameSymbol\
    , game.Date\
    , state.State\
    , game.SeasonYear\
    , game.SeasonType\
    , game.SeasonWeek\
    , instance.Time\
    , instance.Quarter\
    , away.City as AwayTeamCity\
    , away.Name as AwayTeamName\
    , away.DisplayName as AwayTeamDisplayName\
    , away.TwitterAccount as AwayTwitterAccount\
    , away.TwitterHashtag as AwayTwitterHashtag\
    , instance.AwayScore\
    , home.City as HomeTeamCity\
    , home.Name as HomeTeamName\
    , home.DisplayName as HomeTeamDisplayName\
    , home.TwitterAccount as HomeTwitterAccount\
    , home.TwitterHashtag as HomeTwitterHashtag\
    , instance.HomeScore\
    from NFLGameInstances instance\
      inner join NFLGames game\
        on game.GameID = instance.GameID\
        and game.GameSymbol like ?\
      inner join NFLStates state\
        on state.StateID = instance.StateID\
      inner join NFLTeams away\
        on away.TeamID = game.AwayTeamID\
      inner join NFLTeams home\
        on home.TeamID = game.HomeTeamID\
    order by ?? desc limit 1;';
  var params = [game.GameSymbol, 'instance.RecordedOn'];
  return {
    sql: stmnt,
    inserts: params
  };
};

var insertGameChangeTweetQuery = function(tweet) {
  var stmnt =
    'Insert ignore into NFLTweets(InstanceID,TweetString)\
    Select ?,?;';
  var params = [tweet.InstanceID, tweet.TweetString, tweet.InstanceID];
  return {
    sql: stmnt,
    inserts: params
  };
};

var nextTweetQuery = function() {
  var stmnt =
    'Select\
      tweet.TweetID\
    , tweet.TweetString\
    , tweet.RecordedOn\
    from NFLTweets tweet\
      inner join NFLGameInstances instance\
        on instance.InstanceID = tweet.InstanceID\
    where tweet.TwitterID is null\
    and not exists\
      (select 1 from NFLGameInstances newerInstance\
      where newerInstance.GameID = instance.GameID\
      and instance.RecordedOn < newerInstance.RecordedOn)\
    order by tweet.RecordedOn asc limit 1;';
  var params = [];
  return {
    sql: stmnt,
    inserts: params
  };
};

var updateTweetQuery = function(TweetID, TwitterID) {
  var stmnt = 'Update NFLTweets set TwitterID = ? where TweetID = ?;';
  var params = [TwitterID, TweetID];
  return {
    sql: stmnt,
    inserts: params
  };
};

module.exports.leagueInfo = leagueInfo;
module.exports.getGameArray = getGameArray;
module.exports.gameChanged = gameChanged;
module.exports.gameChangeString = gameChangeString;
module.exports.gameChangeTweet = gameChangeTweet;
module.exports.makeGameLink = makeGameLink;
module.exports.insertGameQuery = insertGameQuery;
module.exports.insertGameInstanceQuery = insertGameInstanceQuery;
module.exports.lastGameInstanceQuery = lastGameInstanceQuery;
module.exports.insertGameChangeTweetQuery = insertGameChangeTweetQuery;
module.exports.nextTweetQuery = nextTweetQuery;
module.exports.updateTweetQuery = updateTweetQuery;<commit_msg>bad data pt2 + call reversals<commit_after>var http = require('http'),
  moment = require('moment');

var leagueInfo = {
  leagueName: 'NFL',
  updateURL: 'http://www.nfl.com/liveupdate/scorestrip/ss.json',
  updateURL2: 'http://www.nfl.com/liveupdate/scorestrip/scorestrip.json'
};

var getGameArray = function(next) {
  var rawOuter = '';
  var outerRequest = http.get(leagueInfo.updateURL, function outHttpSetup(oRes) {
    oRes.on('data', function outHttpData(chunk) {
      rawOuter += chunk;
    });
    oRes.on('end', function outHttpEnd() {
      var rawInner = '';
      var innerRequest = http.get(leagueInfo.updateURL2, function inHttpSetup(iRes) {
        iRes.on('data', function inHttpData(chunk) {
          rawInner += chunk;
        });
        iRes.on('end', function inHttpEnd() {
          try {
            rawInner = rawInner.replace(/\,\,/g,',"",')
              .replace(/\,\,/g,',"",');
            innerArray = JSON.parse(rawInner).ss;
            var innerObj = { };
            for (var i = 0; i < innerArray.length; i++) {
              innerObj[innerArray[i][10]] = innerArray[i];
            }
            outerObj = JSON.parse(rawOuter);
            var gameArray = [];
            for (var i = 0; i < outerObj.gms.length; i++) {
              outerObj.gms[i].outer_w = outerObj.w;
              outerObj.gms[i].outer_t = outerObj.t;
              outerObj.gms[i].outer_y = outerObj.y;
              outerObj.gms[i].outer_gd = outerObj.gd;
              outerObj.gms[i].outer_bph = outerObj.bph;
              outerObj.gms[i].score_array = innerObj[outerObj.gms[i].gsis];
              gameArray.push(parseRawGame(outerObj.gms[i]));
            }
            next(null, gameArray);
          } catch(e) {
            console.log('NFL: parsing error');
            e.source = 'NFL';
            next(e);
          }
        });
      }).on('error', function inHttpEnd(e) {
        console.log('NFL inner http Error: ' + e.message);
      });
    });
  }).on('error', function outHttpErrir(e) {
    console.log('NFL outer http Error: ' + e.message);
  });
};

var parseRawGame = function(rawGame) {
  var game = { };
  var gameState = '';
  var timeString = rawGame.t;
  switch(rawGame.q) {
    case 'P':
      gameState = 'Scheduled';
      break;
    case 'H':
      gameState = 'Halftime';
      break;
    case 'F':
      gameState = 'Ended';
      break;
    case 'FO':
      gameState = 'Ended';
      break;
    case '5':
      gameState = 'Overtime';
      break;
    default:
      gameState = 'Progress';
      timeString = rawGame.score_array[3];
      break;
  }
  var endIndex = rawGame.eid.toString().length - 2;
  var dateStr = rawGame.eid.toString().substr(0,endIndex);
  var gameDate = moment(dateStr ,'YYYYMD').toDate();
  game.GameSymbol = rawGame.eid.toString();
  game.State = gameState;
  game.Date = gameDate;
  game.SeasonYear = rawGame.outer_y;
  game.SeasonType = rawGame.outer_t;
  game.SeasonWeek = rawGame.outer_w;
  game.Time = timeString;
  game.Quarter = rawGame.q;
  game.AwayTeamDisplayName = rawGame.v;
  game.AwayTeamName = rawGame.vnn;
  game.AwayScore = rawGame.vs;
  game.HomeTeamDisplayName = rawGame.h;
  game.HomeTeamName = rawGame.hnn;
  game.HomeScore = rawGame.hs;
  game.RawInstance = JSON.stringify(rawGame,null,2);
  return game;
};

var gameChanged = function(oldGame, newGame) {
  if (oldGame.GameSymbol === newGame.GameSymbol) {
    if (oldGame.State !== newGame.State) {
      return true;
    }
    if (oldGame.Quarter !== newGame.Quarter) {
      return true;
    }
    if (oldGame.AwayScore !== newGame.AwayScore ||
      oldGame.HomeScore !== newGame.HomeScore) {
      if ((oldGame.Quarter === newGame.Quarter &&
        oldGame.Time >= newGame.Time) ||
        oldGame.Quarter < newGame.Quarter) {
        return true;
      }
    }
  }
  return false;
  /*return (oldGame.GameSymbol === newGame.GameSymbol) &&
    ( (oldGame.State !== newGame.State) ||
      (oldGame.Quarter !== newGame.Quarter) ||
      ((oldGame.AwayScore !== newGame.AwayScore) ||
      (oldGame.HomeScore !== newGame.HomeScore) &&
      ((oldGame.Quarter === newGame.Quarter &&
        oldGame.Time > newGame.Time) ||
      oldGame.Quarter < newGame.Quarter)));*/
};

var gameChangeString = function(oldGame, newGame) {
  var dif = [];
  if (oldGame.State !== newGame.State)
    dif.push(oldGame.State + '-' + newGame.State);
  if (oldGame.Quarter !== newGame.Quarter)
    dif.push(oldGame.Quarter + '-' + newGame.Quarter);
  if (oldGame.AwayScore !== newGame.AwayScore)
    dif.push(oldGame.AwayScore + '-' + newGame.AwayScore);
  if (oldGame.HomeScore !== newGame.HomeScore)
    dif.push(oldGame.HomeScore + '-' + newGame.HomeScore);
  return dif.join(',');
};

var makeQuarterString = function(quarter) {
  switch (quarter) {
    case '1':
      return '1st';
    case '2':
      return '2nd';
    case '3':
      return '3rd';
    case '4':
      return '4th';
    case '5':
      return 'OT';
    default:
      return '';
  }
}

var gameChangeTweet = function(oldGame, newGame) {
  var tweet = {
    InstanceID: newGame.InstanceID,
    TweetString: ''
  };
  var scores = newGame.AwayTeamName + ' ' + newGame.AwayScore + ', ' +
    newGame.HomeTeamName + ' ' + newGame.HomeScore + ' ';
  if (oldGame.State !== newGame.State) {
    if (oldGame.State === 'Scheduled' && newGame.State === 'Progress') {
      tweet.TweetString = 'Start of game: ' +
        newGame.AwayTeamName + ' vs ' + newGame.HomeTeamName + ' ' +
        makeGameLink(newGame);
    }
   /* if (oldGame.State === 'Progress' && newGame.State === 'Halftime') {
      tweet.TweetString = 'Halftime. ' +
        scores + makeGameLink(newGame);
    }
    if (oldGame.State === 'Halftime' && newGame.State === 'Progress') {
      tweet.TweetString = 'Start of 3rd. ' +
        scores + makeGameLink(newGame);
    }
    if (oldGame.State === 'Progress' && newGame.State === 'Overtime') {
      tweet.TweetString = 'Headed to OT. ' +
        scores + makeGameLink(newGame);
    }*/
    if (newGame.State === 'Ended') {
      if (oldGame.State === 'Overtime') {
        tweet.TweetString = 'Final OT. ' +
          scores + makeGameLink(newGame);
      } else {
        tweet.TweetString = 'Final. ' +
          scores + makeGameLink(newGame);
      }
    }
  /*} else if (oldGame.Quarter !== newGame.Quarter) {
      tweet.TweetString = 'Start of ' + makeQuarterString(newGame.Quarter) + '. ' +
        scores + makeGameLink(newGame);*/
  } else if ((oldGame.Quarter === newGame.Quarter
    && oldGame.Time >= newGame.Time) ||
    (oldGame.Quarter < newGame.Quarter)) {
    if (oldGame.AwayScore > newGame.AwayScore ||
      oldGame.HomeScore > newGame.HomeScore) {
      tweet.TweetString = 'Score correction. ' +
        scores + newGame.Time + ' ' +
        makeQuarterString(newGame.Quarter) + ' ' +
        makeGameLink(newGame);
    } else if (oldGame.AwayScore !== newGame.AwayScore) {
      tweet.TweetString = newGame.AwayTeamName + ' score. ' +
        scores + newGame.Time + ' ' +
        makeQuarterString(newGame.Quarter) + ' ' +
        makeGameLink(newGame);
    } else if (oldGame.HomeScore !== newGame.HomeScore) {
      tweet.TweetString = newGame.HomeTeamName + ' score. ' +
        scores + newGame.Time + ' ' +
        makeQuarterString(newGame.Quarter) + ' ' +
        makeGameLink(newGame);
    }
  }
  return tweet;
};

var makeGameLink = function(game) {
  var link = 'http://www.nfl.com/gamecenter/' +
    game.GameSymbol +
    '/' +
    game.SeasonYear +
    '/' +
    game.SeasonType +
    game.SeasonWeek +
    '/' +
    game.AwayTeamName +
    '@' +
    game.HomeTeamName;
  return link;
};

var insertGameQuery = function(game) {
  var stmnt =
    'Insert into NFLGames(GameSymbol,Date,SeasonYear,SeasonType,\
      SeasonWeek,AwayTeamID,HomeTeamID)\
    Select\
      ?,?,?,?,?,away.TeamID,home.TeamID\
    from NFLTeams away\
      inner join NFLTeams home\
        on LOWER(REPLACE(home.Name,\' \',\'\')) like ?\
    where LOWER(REPLACE(away.Name,\' \',\'\')) like ?\
    and not exists\
      (Select 1 from NFLGames where GameSymbol like ?);';
  var params = [game.GameSymbol, game.Date, game.SeasonYear, game.SeasonType,
    game.SeasonWeek, game.HomeTeamName, game.AwayTeamName, game.GameSymbol];
  return {
    sql: stmnt,
    inserts: params
  };
};

var insertGameInstanceQuery = function(game) {
  var stmnt =
    'Insert into NFLGameInstances(GameID,StateID,Time,\
      Quarter,AwayScore,HomeScore,RawInstance)\
    Select\
      game.GameID,state.StateID,?,?,?,?,?\
    from NFLStates state\
      inner join NFLGames game\
        on game.GameSymbol like ?\
    where state.State = ?;';
  var params = [game.Time, game.Quarter, game.AwayScore,
    game.HomeScore, game.RawInstance, game.GameSymbol, game.State];
  return {
    sql: stmnt,
    inserts: params
  };
};

var lastGameInstanceQuery = function(game) {
  var stmnt =
    'Select\
      instance.InstanceID\
    , game.GameSymbol\
    , game.Date\
    , state.State\
    , game.SeasonYear\
    , game.SeasonType\
    , game.SeasonWeek\
    , instance.Time\
    , instance.Quarter\
    , away.City as AwayTeamCity\
    , away.Name as AwayTeamName\
    , away.DisplayName as AwayTeamDisplayName\
    , away.TwitterAccount as AwayTwitterAccount\
    , away.TwitterHashtag as AwayTwitterHashtag\
    , instance.AwayScore\
    , home.City as HomeTeamCity\
    , home.Name as HomeTeamName\
    , home.DisplayName as HomeTeamDisplayName\
    , home.TwitterAccount as HomeTwitterAccount\
    , home.TwitterHashtag as HomeTwitterHashtag\
    , instance.HomeScore\
    from NFLGameInstances instance\
      inner join NFLGames game\
        on game.GameID = instance.GameID\
        and game.GameSymbol like ?\
      inner join NFLStates state\
        on state.StateID = instance.StateID\
      inner join NFLTeams away\
        on away.TeamID = game.AwayTeamID\
      inner join NFLTeams home\
        on home.TeamID = game.HomeTeamID\
    order by ?? desc limit 1;';
  var params = [game.GameSymbol, 'instance.RecordedOn'];
  return {
    sql: stmnt,
    inserts: params
  };
};

var insertGameChangeTweetQuery = function(tweet) {
  var stmnt =
    'Insert ignore into NFLTweets(InstanceID,TweetString)\
    Select ?,?;';
  var params = [tweet.InstanceID, tweet.TweetString, tweet.InstanceID];
  return {
    sql: stmnt,
    inserts: params
  };
};

var nextTweetQuery = function() {
  var stmnt =
    'Select\
      tweet.TweetID\
    , tweet.TweetString\
    , tweet.RecordedOn\
    from NFLTweets tweet\
      inner join NFLGameInstances instance\
        on instance.InstanceID = tweet.InstanceID\
    where tweet.TwitterID is null\
    and not exists\
      (select 1 from NFLGameInstances newerInstance\
      where newerInstance.GameID = instance.GameID\
      and instance.RecordedOn < newerInstance.RecordedOn)\
    order by tweet.RecordedOn asc limit 1;';
  var params = [];
  return {
    sql: stmnt,
    inserts: params
  };
};

var updateTweetQuery = function(TweetID, TwitterID) {
  var stmnt = 'Update NFLTweets set TwitterID = ? where TweetID = ?;';
  var params = [TwitterID, TweetID];
  return {
    sql: stmnt,
    inserts: params
  };
};

module.exports.leagueInfo = leagueInfo;
module.exports.getGameArray = getGameArray;
module.exports.gameChanged = gameChanged;
module.exports.gameChangeString = gameChangeString;
module.exports.gameChangeTweet = gameChangeTweet;
module.exports.makeGameLink = makeGameLink;
module.exports.insertGameQuery = insertGameQuery;
module.exports.insertGameInstanceQuery = insertGameInstanceQuery;
module.exports.lastGameInstanceQuery = lastGameInstanceQuery;
module.exports.insertGameChangeTweetQuery = insertGameChangeTweetQuery;
module.exports.nextTweetQuery = nextTweetQuery;
module.exports.updateTweetQuery = updateTweetQuery;<|endoftext|>


<commit_before>import { settingsHelper } from './SettingsHelper';
import { disputeHelper } from './DisputeHelper';
import { backChainUtil } from './BackChainUtil';
import { dbconnectionManager } from './DBConnectionManager';
import moment from 'moment';
import config from './config';

class DisputeOrganizerTaskHelper {

    executeTask() {
        console.log("Deleting old disputes && getting enterprise account number mappings from PLT.");
        settingsHelper.getApplicationSettings()
            .then(result => {
                this.deleteOldDisputes(result);
                this.getEnterpriseAccountMapping(result);
                setTimeout(() => {
                    this.executeTask();
                }, config.disputeOrganizerIntervalInMillis);
            })
            .catch(err => {
                setTimeout(() => {
                    this.executeTask();
                }, config.disputeOrganizerIntervalInMillis);
                console.error("submitDisputeWindowVisibleForTnx err: " + err);
            });
    }

    isSubmissionWindowOver(transaction, settings) {
        if (settings && settings.blockChain.disputeSubmissionWindowInMinutes) {
            let disputeSubmissionWindowInMinutes = settings.blockChain.disputeSubmissionWindowInMinutes;
            let duration = moment.duration(moment(new Date()).diff(moment(new Date(transaction.date))));
            let durationInMinutes = Math.ceil(duration.asMinutes());
            return durationInMinutes > (disputeSubmissionWindowInMinutes+1440);//1440 is no. of mins in One day. We have to keep expired disputes for one day.
        } else {
            return true;
        }
    }

    getEnterpriseAccountMapping(settings) {
        if (settings && settings.chainOfCustidy && settings.chainOfCustidy.authenticationToken && settings.chainOfCustidy.chainOfCustodyUrl) {
            fetch(backChainUtil.returnValidURL(settings.chainOfCustidy.chainOfCustodyUrl + '/oms/rest/backchain/v1/addresses'), {
                method: 'get',
                headers: new Headers({
                    'Cache-Control': 'no-cache',
                    'Pragma': 'no-cache',
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Authorization': 'token ' + settings.chainOfCustidy.authenticationToken
                })
            }).then(response => {
                return response.json();
            }).then(result => {
                if (!result) {
                    console.error("Couldn't get back chain address -> enterprise mapping.");
                }
                if (result.length > 0) {
                    let backChainAddressMappings = {};
                    for (let i = 0; i < result.length; i++) {
                        Object.assign(backChainAddressMappings, result[i]);
                    }
                    this.writeBackChainAddressMapping(backChainAddressMappings);
                }
            }).catch(error => {
                console.error("Couldn't get back chain address -> enterprise mapping. Error:" + error);
            });
        }
    }

    writeBackChainAddressMapping(backChainAddressMappings) {
        return new Promise((resolve, reject) => {
            dbconnectionManager.getConnection().collection('BackChainAddressMapping').update({}, backChainAddressMappings, { upsert: true })
                .then(() => {
                    console.info("BackChainAddressMapping written successfully !");
                    resolve({ success: true });
                })
                .catch((err) => {
                    console.error("Error occurred while writing BackChainAddressMapping: " + err);
                    reject(err);
                });
        });
    }

    deleteOldDisputes(settings) {
        let me = this;
        disputeHelper.getDisputes({status:JSON.stringify(['DRAFT'])})
            .then(function (result) {
                let dispute = null;
                for (let i = 0, len = result.length; i < len; i++) {
                    dispute = result[i];
                    if (dispute.transaction) {
                        if (me.isSubmissionWindowOver(dispute.transaction, settings)) {
                            disputeHelper.discardDraftDispute(dispute.disputeId)
                                .then(function (result) {
                                    if (result.success) {
                                        console.log("Old disputes deleted successfully.");
                                    }
                                })
                        }
                    }
                }
            })
            .catch(function (error) {
                console.log("Some error occured while deleting old disputes " + error);
            });
    }
}

export const disputeOrganizerTaskHelper = new DisputeOrganizerTaskHelper();<commit_msg>update dispute submission time<commit_after>import { settingsHelper } from './SettingsHelper';
import { disputeHelper } from './DisputeHelper';
import { backChainUtil } from './BackChainUtil';
import { dbconnectionManager } from './DBConnectionManager';
import moment from 'moment';
import config from './config';
import oneBcClient from '@onenetwork/one-backchain-client';

class DisputeOrganizerTaskHelper {

    executeTask() {
        console.log("Deleting old disputes && getting enterprise account number mappings from PLT.");
        settingsHelper.getApplicationSettings()
            .then(result => {
                this.deleteOldDisputes(result);
                this.getEnterpriseAccountMapping(result);
                this.updateDisputeSubmissionTime(result);
                setTimeout(() => {
                    this.executeTask();
                }, config.disputeOrganizerIntervalInMillis);
            })
            .catch(err => {
                setTimeout(() => {
                    this.executeTask();
                }, config.disputeOrganizerIntervalInMillis);
                console.error("submitDisputeWindowVisibleForTnx err: " + err);
            });
    }

    isSubmissionWindowOver(transaction, settings) {
        if (settings && settings.blockChain.disputeSubmissionWindowInMinutes) {
            let disputeSubmissionWindowInMinutes = settings.blockChain.disputeSubmissionWindowInMinutes;
            let duration = moment.duration(moment(new Date()).diff(moment(new Date(transaction.date))));
            let durationInMinutes = Math.ceil(duration.asMinutes());
            return durationInMinutes > (disputeSubmissionWindowInMinutes+1440);//1440 is no. of mins in One day. We have to keep expired disputes for one day.
        } else {
            return true;
        }
    }

    getEnterpriseAccountMapping(settings) {
        if (settings && settings.chainOfCustidy && settings.chainOfCustidy.authenticationToken && settings.chainOfCustidy.chainOfCustodyUrl) {
            fetch(backChainUtil.returnValidURL(settings.chainOfCustidy.chainOfCustodyUrl + '/oms/rest/backchain/v1/addresses'), {
                method: 'get',
                headers: new Headers({
                    'Cache-Control': 'no-cache',
                    'Pragma': 'no-cache',
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Authorization': 'token ' + settings.chainOfCustidy.authenticationToken
                })
            }).then(response => {
                return response.json();
            }).then(result => {
                if (!result) {
                    console.error("Couldn't get back chain address -> enterprise mapping.");
                }
                if (result.length > 0) {
                    let backChainAddressMappings = {};
                    for (let i = 0; i < result.length; i++) {
                        Object.assign(backChainAddressMappings, result[i]);
                    }
                    this.writeBackChainAddressMapping(backChainAddressMappings);
                }
            }).catch(error => {
                console.error("Couldn't get back chain address -> enterprise mapping. Error:" + error);
            });
        }
    }

    writeBackChainAddressMapping(backChainAddressMappings) {
        return new Promise((resolve, reject) => {
            dbconnectionManager.getConnection().collection('BackChainAddressMapping').update({}, backChainAddressMappings, { upsert: true })
                .then(() => {
                    console.info("BackChainAddressMapping written successfully !");
                    resolve({ success: true });
                })
                .catch((err) => {
                    console.error("Error occurred while writing BackChainAddressMapping: " + err);
                    reject(err);
                });
        });
    }

    deleteOldDisputes(settings) {
        let me = this;
        disputeHelper.getDisputes({status:JSON.stringify(['DRAFT'])})
            .then(function (result) {
                let dispute = null;
                for (let i = 0, len = result.length; i < len; i++) {
                    dispute = result[i];
                    if (dispute.transaction) {
                        if (me.isSubmissionWindowOver(dispute.transaction, settings)) {
                            disputeHelper.discardDraftDispute(dispute.disputeId)
                                .then(function (result) {
                                    if (result.success) {
                                        console.log("Old disputes deleted successfully.");
                                    }
                                })
                        }
                    }
                }
            })
            .catch(function (error) {
                console.log("Some error occured while deleting old disputes " + error);
            });
    }

    updateDisputeSubmissionTime(settings) {
        let disputeBcClient = oneBcClient.createDisputeBcClient({
            blockchain: 'eth',
            url: settings.blockChain.url,
            contentBackchainContractAddress: settings.blockChain.contractAddress,
            disputeBackchainContractAddress: settings.blockChain.disputeContractAddress
        });

        disputeBcClient.getDisputeSubmissionWindowInMinutes()
        .then(function(result){
            settings.blockChain.disputeSubmissionWindowInMinutes = parseInt(result);
            dbconnectionManager.getConnection().collection('Settings')
            .update({}, {$set: settings}, { upsert: true })
            .then(function (result) {
                if (result.result.nModified > 0) {
                    console.log("Settings disputeSubmissionTime updated successfully");
                }
            }).catch(function (err) {
                console.error("Error while updating disputeSubmissionTime in Settings collection" + err);
            });
        }).
        catch(function(error) {
            console.error("Error while updating disputeSubmissionTime in Settings collection" + err);
        });
    }
}

export const disputeOrganizerTaskHelper = new DisputeOrganizerTaskHelper();<|endoftext|>


<commit_before>// Service that propagates business process flow changes

'use strict';

var aFrom           = require('es5-ext/array/from')
  , ensureIterable  = require('es5-ext/iterable/validate-object')
  , ensureCallable  = require('es5-ext/object/valid-callable')
  , Set             = require('es6-set')
  , ensureType      = require('dbjs/valid-dbjs-type')
  , debug           = require('debug-ext')('business-process-flow')
  , resolveStepPath = require('../../utils/resolve-processing-step-full-path')
  , setupTriggers   = require('../_setup-triggers');

module.exports = function (BusinessProcessType, stepShortPaths/*, options*/) {
	var businessProcesses = ensureType(BusinessProcessType).instances
		.filterByKey('isFromEregistrations', true).filterByKey('isDemo', false);
	var options = Object(arguments[2]), customStepReturnHandler, onStepRedelegate, onStepStatus
	  , onUserProcessingEnd;
	if (options.customStepReturnHandler != null) {
		customStepReturnHandler = ensureCallable(options.customStepReturnHandler);
	}
	if (options.onStepRedelegate != null) onStepRedelegate = ensureCallable(options.onStepRedelegate);
	if (options.onStepStatus != null) onStepStatus = ensureCallable(options.onStepStatus);
	if (options.onUserProcessingEnd != null) {
		onUserProcessingEnd = ensureCallable(options.onUserProcessingEnd);
	}

	var stepPaths = aFrom(ensureIterable(stepShortPaths)).map(function (shortPath) {
		return 'processingSteps/map/' + resolveStepPath(shortPath);
	});

	// Business process: isSubmitted
	setupTriggers({
		trigger: businessProcesses.filterByKey('isSubmittedReady', true)
			.filterByKey('isSubmitted', false)
	}, function (businessProcess) {
		debug('%s submitted', businessProcess.__id__);
		businessProcess.isSubmitted = true;
	});

	// Below two triggers are needed just for migration phase
	// After that there's no scenario when they may trigger, therefore should be removed
	setupTriggers({
		trigger: businessProcesses.filterByKey('isSentBack', true)
			.filterByKey('isSubmitted', false)
	}, function (businessProcess) {
		debug('%s submitted (through sent back)', businessProcess.__id__);
		businessProcess.isSubmitted = true;
	});
	setupTriggers({
		trigger: businessProcesses.filterByKey('isUserProcessing', true)
			.filterByKey('isSubmitted', false)
	}, function (businessProcess) {
		debug('%s submitted (through user processing)', businessProcess.__id__);
		businessProcess.isSubmitted = true;
	});

	var businessProcessesSubmitted = businessProcesses.filterByKey('isSubmitted', true);

	// Business process: sentBack finalization
	setupTriggers({
		trigger: businessProcessesSubmitted.filterByKey('isSubmittedReady', true)
			.filterByKey('isSentBack', true)
	}, function (businessProcess) {
		debug('%s finalize sentBack', businessProcess.__id__);
		businessProcess.delete('isSentBack');
	});

	// Business process: isUserProcessing initialization
	setupTriggers({
		trigger: businessProcessesSubmitted.filterByKey('isUserProcessingReady', true)
			.filterByKey('isUserProcessing', false)
	}, function (businessProcess) {
		debug('%s initialize user processing', businessProcess.__id__);
		businessProcess.submissionForms.delete('isAffidavitSigned');
		businessProcess.isUserProcessing = true;
	});

	// Business process: isUserProcessing finalization
	setupTriggers({
		trigger: businessProcessesSubmitted.filterByKey('isSubmittedReady', true)
			.filterByKey('isUserProcessing', true)
	}, function (businessProcess) {
		debug('%s finalize user processing', businessProcess.__id__);
		if (onUserProcessingEnd) onUserProcessingEnd(businessProcess);
		businessProcess.delete('isUserProcessing');
	});

	// Business process: isApproved preservation
	setupTriggers({
		trigger: businessProcessesSubmitted.filterByKey('isApproved', true)
	}, function (businessProcess) {
		if (businessProcess.hasOwnProperty('isApproved')) return;
		debug('%s approved', businessProcess.__id__);
		businessProcess.isApproved = true;
	});

	// Processing steps:
	var nonFinalStatuses = new Set(['pending', 'paused']);
	stepPaths.forEach(function (stepPath, index) {
		// status
		setupTriggers({
			trigger: businessProcessesSubmitted.filterByKeyPath(stepPath + '/status', function (value) {
				return value && !nonFinalStatuses.has(value);
			})
		}, function (businessProcess) {
			var step = businessProcess.getBySKeyPath(stepPath), targetStep;
			if (step.hasOwnProperty('status')) return; // Already shadowed
			debug('%s %s step %s', businessProcess.__id__,
				step.shortPath, step.status);
			if (onStepStatus) onStepStatus(step);
			if (!step.hasOwnProperty('isReady')) step.set('isReady', true);
			if (step.revisionStatus && !nonFinalStatuses.has(step.revisionStatus)) {
				step.set('revisionStatus', step.revisionStatus);
			}
			step.set('status', step.status);

			if (step.status === 'sentBack') {
				if (businessProcess.isSentBack) return;

				// Sent back initialization
				debug('%s sent back by %s step', businessProcess.__id__, step.shortPath);
				businessProcess.submissionForms.delete('isAffidavitSigned');
				businessProcess.isSentBack = true;

			} else if (step.status === 'redelegated') {

				// Redelegation initialization
				targetStep = step.redelegatedTo;
				debug('%s redelegated to %s from %s', businessProcess.__id__,
					targetStep.shortPath, step.shortPath);
				if (onStepRedelegate) onStepRedelegate(step);
				if (targetStep.hasOwnProperty('revisionOfficialStatus')) {
					targetStep.delete('revisionOfficialStatus');
				}
				targetStep.delete('officialStatus');
				if (targetStep.hasOwnProperty('revisionStatus')) targetStep.delete('revisionStatus');
				targetStep.delete('status');
				if (targetStep.hasOwnProperty('isSatisfied')) targetStep.delete('isSatisfied');
			} else if (step.status === 'rejected') {

				// Business process rejection
				debug('%s rejected at %s', businessProcess.__id__, step.shortPath);
				businessProcess.isRejected = true;
			}
		});

		// isSatisfied
		setupTriggers({
			trigger: businessProcessesSubmitted.filterByKeyPath(stepPath + '/isSatisfiedReady', true)
				.filterByKeyPath(stepPath + '/isSatisfied', false)
		}, function (businessProcess) {
			var step = businessProcess.getBySKeyPath(stepPath);
			debug('%s %s step satisfied', businessProcess.__id__,
				step.shortPath);
			step.isSatisfied = true;
		});

		// Valid returns
		var returnStatuses = new Set(['sentBack', 'redelegated']);
		setupTriggers({
			preTrigger: businessProcessesSubmitted
				.filterByKeyPath(stepPath + '/isPreviousStepsSatisfiedDeep', false),
			trigger: businessProcessesSubmitted
				.filterByKeyPath(stepPath + '/isPreviousStepsSatisfiedDeep', true)
		}, function (businessProcess) {
			var step = businessProcess.getBySKeyPath(stepPath);
			if (!step.isApplicable) return;
			if (!step.status || (step.status === 'pending')) return;
			if (customStepReturnHandler) customStepReturnHandler(step);
			if (step.statusComputed && (step.statusComputed !== 'pending') &&
					!returnStatuses.has(step.status)) {
				return;
			}
			debug('%s %s step reset from %s to pending state', businessProcess.__id__,
				step.shortPath, step.status);
			if (step.hasOwnProperty('revisionOfficialStatus')) step.delete('revisionOfficialStatus');
			step.delete('officialStatus');
			if (step.hasOwnProperty('revisionStatus')) step.delete('revisionStatus');
			step.delete('status');
			if (step.hasOwnProperty('isSatisfied')) step.delete('isSatisfied');
		});
	});
};
<commit_msg>-- convention<commit_after>// Service that propagates business process flow changes

'use strict';

var aFrom           = require('es5-ext/array/from')
  , ensureIterable  = require('es5-ext/iterable/validate-object')
  , ensureCallable  = require('es5-ext/object/valid-callable')
  , Set             = require('es6-set')
  , ensureType      = require('dbjs/valid-dbjs-type')
  , debug           = require('debug-ext')('business-process-flow')
  , resolveStepPath = require('../../utils/resolve-processing-step-full-path')
  , setupTriggers   = require('../_setup-triggers');

module.exports = function (BusinessProcessType, stepShortPaths/*, options*/) {
	var businessProcesses = ensureType(BusinessProcessType).instances
		.filterByKey('isFromEregistrations', true).filterByKey('isDemo', false);
	var options = Object(arguments[2]), customStepReturnHandler, onStepRedelegate, onStepStatus
	  , onUserProcessingEnd;
	if (options.customStepReturnHandler != null) {
		customStepReturnHandler = ensureCallable(options.customStepReturnHandler);
	}
	if (options.onStepRedelegate != null) onStepRedelegate = ensureCallable(options.onStepRedelegate);
	if (options.onStepStatus != null) onStepStatus = ensureCallable(options.onStepStatus);
	if (options.onUserProcessingEnd != null) {
		onUserProcessingEnd = ensureCallable(options.onUserProcessingEnd);
	}

	var stepPaths = aFrom(ensureIterable(stepShortPaths)).map(function (shortPath) {
		return 'processingSteps/map/' + resolveStepPath(shortPath);
	});

	// Business process: isSubmitted
	setupTriggers({
		trigger: businessProcesses.filterByKey('isSubmittedReady', true)
			.filterByKey('isSubmitted', false)
	}, function (businessProcess) {
		debug('%s submitted', businessProcess.__id__);
		businessProcess.isSubmitted = true;
	});

	// Below two triggers are needed just for migration phase
	// After that there's no scenario when they may trigger, therefore should be removed
	setupTriggers({
		trigger: businessProcesses.filterByKey('isSentBack', true)
			.filterByKey('isSubmitted', false)
	}, function (businessProcess) {
		debug('%s submitted (through sent back)', businessProcess.__id__);
		businessProcess.isSubmitted = true;
	});
	setupTriggers({
		trigger: businessProcesses.filterByKey('isUserProcessing', true)
			.filterByKey('isSubmitted', false)
	}, function (businessProcess) {
		debug('%s submitted (through user processing)', businessProcess.__id__);
		businessProcess.isSubmitted = true;
	});

	var businessProcessesSubmitted = businessProcesses.filterByKey('isSubmitted', true);

	// Business process: sentBack finalization
	setupTriggers({
		trigger: businessProcessesSubmitted.filterByKey('isSubmittedReady', true)
			.filterByKey('isSentBack', true)
	}, function (businessProcess) {
		debug('%s finalize sentBack', businessProcess.__id__);
		businessProcess.delete('isSentBack');
	});

	// Business process: isUserProcessing initialization
	setupTriggers({
		trigger: businessProcessesSubmitted.filterByKey('isUserProcessingReady', true)
			.filterByKey('isUserProcessing', false)
	}, function (businessProcess) {
		debug('%s initialize user processing', businessProcess.__id__);
		businessProcess.submissionForms.delete('isAffidavitSigned');
		businessProcess.isUserProcessing = true;
	});

	// Business process: isUserProcessing finalization
	setupTriggers({
		trigger: businessProcessesSubmitted.filterByKey('isSubmittedReady', true)
			.filterByKey('isUserProcessing', true)
	}, function (businessProcess) {
		debug('%s finalize user processing', businessProcess.__id__);
		if (onUserProcessingEnd) onUserProcessingEnd(businessProcess);
		businessProcess.delete('isUserProcessing');
	});

	// Business process: isApproved preservation
	setupTriggers({
		trigger: businessProcessesSubmitted.filterByKey('isApproved', true)
	}, function (businessProcess) {
		if (businessProcess.hasOwnProperty('isApproved')) return;
		debug('%s approved', businessProcess.__id__);
		businessProcess.isApproved = true;
	});

	// Processing steps:
	var nonFinalStatuses = new Set(['pending', 'paused']);
	stepPaths.forEach(function (stepPath, index) {
		// status
		setupTriggers({
			trigger: businessProcessesSubmitted.filterByKeyPath(stepPath + '/status', function (value) {
				return value && !nonFinalStatuses.has(value);
			})
		}, function (businessProcess) {
			var step = businessProcess.getBySKeyPath(stepPath), targetStep;
			if (step.hasOwnProperty('status')) return; // Already shadowed
			debug('%s %s step %s', businessProcess.__id__,
				step.shortPath, step.status);
			if (onStepStatus) onStepStatus(step);
			if (!step.hasOwnProperty('isReady')) step.isReady = true;
			if (step.revisionStatus && !nonFinalStatuses.has(step.revisionStatus)) {
				step.set('revisionStatus', step.revisionStatus);
			}
			step.set('status', step.status);

			if (step.status === 'sentBack') {
				if (businessProcess.isSentBack) return;

				// Sent back initialization
				debug('%s sent back by %s step', businessProcess.__id__, step.shortPath);
				businessProcess.submissionForms.delete('isAffidavitSigned');
				businessProcess.isSentBack = true;

			} else if (step.status === 'redelegated') {

				// Redelegation initialization
				targetStep = step.redelegatedTo;
				debug('%s redelegated to %s from %s', businessProcess.__id__,
					targetStep.shortPath, step.shortPath);
				if (onStepRedelegate) onStepRedelegate(step);
				if (targetStep.hasOwnProperty('revisionOfficialStatus')) {
					targetStep.delete('revisionOfficialStatus');
				}
				targetStep.delete('officialStatus');
				if (targetStep.hasOwnProperty('revisionStatus')) targetStep.delete('revisionStatus');
				targetStep.delete('status');
				if (targetStep.hasOwnProperty('isSatisfied')) targetStep.delete('isSatisfied');
			} else if (step.status === 'rejected') {

				// Business process rejection
				debug('%s rejected at %s', businessProcess.__id__, step.shortPath);
				businessProcess.isRejected = true;
			}
		});

		// isSatisfied
		setupTriggers({
			trigger: businessProcessesSubmitted.filterByKeyPath(stepPath + '/isSatisfiedReady', true)
				.filterByKeyPath(stepPath + '/isSatisfied', false)
		}, function (businessProcess) {
			var step = businessProcess.getBySKeyPath(stepPath);
			debug('%s %s step satisfied', businessProcess.__id__,
				step.shortPath);
			step.isSatisfied = true;
		});

		// Valid returns
		var returnStatuses = new Set(['sentBack', 'redelegated']);
		setupTriggers({
			preTrigger: businessProcessesSubmitted
				.filterByKeyPath(stepPath + '/isPreviousStepsSatisfiedDeep', false),
			trigger: businessProcessesSubmitted
				.filterByKeyPath(stepPath + '/isPreviousStepsSatisfiedDeep', true)
		}, function (businessProcess) {
			var step = businessProcess.getBySKeyPath(stepPath);
			if (!step.isApplicable) return;
			if (!step.status || (step.status === 'pending')) return;
			if (customStepReturnHandler) customStepReturnHandler(step);
			if (step.statusComputed && (step.statusComputed !== 'pending') &&
					!returnStatuses.has(step.status)) {
				return;
			}
			debug('%s %s step reset from %s to pending state', businessProcess.__id__,
				step.shortPath, step.status);
			if (step.hasOwnProperty('revisionOfficialStatus')) step.delete('revisionOfficialStatus');
			step.delete('officialStatus');
			if (step.hasOwnProperty('revisionStatus')) step.delete('revisionStatus');
			step.delete('status');
			if (step.hasOwnProperty('isSatisfied')) step.delete('isSatisfied');
		});
	});
};
<|endoftext|>


<commit_before>var ResourceService = require("services/ResourceService");
var ItemListService = require("services/ItemListService");

Vue.component("item-search", {

    template: "#vue-item-search",

    data: function()
    {
        return {
            searchString: "",
            itemSearch: {}
        };
    },

    ready: function()
    {
        ResourceService.bind("itemSearch", this);
        this.initAutocomplete();
    },

    methods:
    {
        search: function()
        {
            if (document.location.pathname === "/search")
            {
                ItemListService.setSearchString(this.itemSearch.searchString);
            }
            else
            {
                window.open("/search?searchString=" + this.itemSearch.searchString, "_self", false);
            }
        },

        initAutocomplete: function()
        {
            var self = this;

            $(".search-input").autocomplete({
                serviceUrl: "/rest/item/search/autocomplete",
                paramName: "searchString",
                width: $(".search-box-shadow-frame").width(),
                zIndex: 1061,
                maxHeight: 310,
                minChars: 2,
                preventBadQueries: false,
                onSelect: function(suggestion)
                {
                    self.itemSearch.searchString = suggestion.value;
                },
                beforeRender: function()
                {
                    $(".autocomplete-suggestions").width($(".search-box-shadow-frame").width());
                },
                transformResult: function(response)
                {
                    return self.transformSuggestionResult(response);
                }
            });

            $(window).resize(function()
            {
                $(".autocomplete-suggestions").width($(".search-box-shadow-frame").width());
            });
        },

        transformSuggestionResult: function(result)
        {
            result = JSON.parse(result);
            var suggestions =
                {
                    suggestions: $.map(result.data.documents, function(dataItem)
                    {
                        var value = dataItem.data.texts[0].name1;

                        return {
                            value: value,
                            data : value
                        };
                    })
                };

            return suggestions;
        }
    }
});
<commit_msg>UPDATE autocomplete<commit_after>var ResourceService = require("services/ResourceService");
var ItemListService = require("services/ItemListService");

Vue.component("item-search", {

    template: "#vue-item-search",

    data: function()
    {
        return {
            searchString: "",
            itemSearch: {}
        };
    },

    ready: function()
    {
        ResourceService.bind("itemSearch", this);
        this.initAutocomplete();
    },

    methods:
    {
        search: function()
        {
            if (document.location.pathname === "/search")
            {
                ItemListService.setSearchString(this.itemSearch.searchString);
            }
            else
            {
                window.open("/search?searchString=" + this.itemSearch.searchString, "_self", false);
            }
        },

        initAutocomplete: function()
        {
            var self = this;

            $(".search-input").autocomplete({
                serviceUrl: "/rest/item/search/autocomplete",
                paramName: "searchString",
                width: $(".search-box-shadow-frame").width(),
                zIndex: 1061,
                maxHeight: 310,
                minChars: 2,
                preventBadQueries: false,
                onSelect: function(suggestion)
                {
                    self.itemSearch.searchString = suggestion.value;
                    self.search();
                },
                beforeRender: function()
                {
                    $(".autocomplete-suggestions").width($(".search-box-shadow-frame").width());
                },
                transformResult: function(response)
                {
                    return self.transformSuggestionResult(response);
                }
            });

            $(window).resize(function()
            {
                $(".autocomplete-suggestions").width($(".search-box-shadow-frame").width());
            });
        },

        transformSuggestionResult: function(result)
        {
            result = JSON.parse(result);
            var suggestions =
                {
                    suggestions: $.map(result.data.documents, function(dataItem)
                    {
                        var value = dataItem.data.texts[0].name1;

                        return {
                            value: value,
                            data : value
                        };
                    })
                };

            return suggestions;
        }
    }
});
<|endoftext|>


<commit_before>// Tiny profiler
//
// Enable by setting the environment variable `METEOR_PROFILE`.
//
// The main entry point is `Profile`, which wraps an existing function
// and returns a new function which, when called, calls the original
// function and profiles it.
//
// before:
//
//     foo: function (a) {
//       return a + this.b;
//     },
//
// after:
//
//     foo: Profile("foo", function (a) {
//       return a + this.b;
//     }),
//
// The advantage of this form is that it doesn't change the
// indentation of the wrapped code, which makes merging changes from
// other code branches easier.
//
// If profiling is disabled (if `METEOR_PROFILE` isn't set), `Profile`
// simply returns the original function.
//
// To run a profiling session and print the report, call `Profile.run`:
//
//     var createBundle = function () {
//       Profile.run("bundle", function () {
//         ...code to create the bundle which includes calls to `Profile`.
//       });
//     };
//
// Code is not profiled when called outside of a `Profile.run`, so the
// times in the report only include the time spent inside of the call
// to `Profile.run`.
//
// Sometimes you'll want to use a name for the profile bucket which
// depends on the arguments passed to the function or the value of
// `this`.  In this case you can pass a function for the bucket
// argument, which will be called to get the bucket name.
//
// before:
//     build: function (target) {
//       ... build target ...
//     },
//
// after:
//     build: Profile(
//       function (target) { return "build " + target; },
//       function (target) {
//         ... build target ...
//       }),
//
// But if it's easier, you can use `Profile.time` instead, which
// immediately calls the passed function with no arguments and
// profiles it, and returns what the function returns.
//
//     foo: function (a) {
//       var self = this;
//       return Profile.time("foo", function () {
//         return a + self.b;
//       });
//     },
//
//     build: function (target) {
//       var self = this;
//       self.doSomeSetup();
//       Profile.time("build " + target, function () {
//         ... build target ...
//       });
//       self.doSomeCleanup();
//     },
//
// The disadvantage is that you end up changing the indentation of the
// profiled code, which makes merging branches more painful.  But you
// can profile anywhere in the code; you don't have to just profile at
// function boundaries.
//
// Note profiling code will itself add a bit of execution time.
// If you profile in a tight loop and your total execution time is
// going up, you're probably starting to profile how long it takes to
// profile things :).
//
// If another profile (such as "compile js") is called while the first
// function is currently being profiled, this creates an entry like
// this:
//
//    build client : compile js
//
// which can continue to be nested, e.g.,
//
//    build client : compile js : read source files
//
// The total time reported for a bucket such as "build client" doesn't
// change regardless of whether it has child entries or not.  However,
// if an entry has child entries, it automatically gets an "other"
// entry:
//
//     build client: 400.0
//       compile js: 300.0
//         read source files: 20.0
//         other compile js: 280.0
//       other build client: 100.0
//
// The "other" entry reports how much time was spent in the "build
// client" entry not spent in the other child entries.
//
// The are two reports displayed: the hierarchical report and the
// leaf time report.  The hierarchical report looks like the example
// above and shows how much time was spent in each entry within its
// parent entry.
//
// The primary purpose of the hierarchical report is to be able to see
// where times are unaccounted for.  If you see a lot of time being
// spent in an "other" bucket, and you don't know what it is, you can
// add more profiling to dig deeper.
//
// The leaf time report shows the total time spent within leaf
// buckets.  For example, if if multiple steps have "read source
// files", the leaf time reports shows the total amount of time spent
// in "read source files" across all calls.
//
// Once you see in the hierarchical report that you have a good handle
// on accounting for most of the time, the leaf report shows you which
// buckets are the most expensive.
//
// By only including leaf buckets, the times in the leaf report are
// non-overlapping.  (The total of the times equals the elapsed time
// being profiled).
//
// For example, suppose "A" is profiled for a total time of 200ms, and
// that includes a call to "B" of 150ms:
//
//     B: 150
//     A (without B): 50
//
// and suppose there's another call to "A" which *doesn't* include a
// call to "B":
//
//     A: 300
//
// and there's a call to "B" directly:
//
//     B: 100
//
// All for a total time of 600ms.  In the hierarchical report, this
// looks like:
//
//     A: 500.0
//       B: 150.0
//       other A: 350.0
//     B: 100.0
//
// and in the leaf time report:
//
//     other A: 350.0
//     B: 250.0
//
// In both reports the grand total is 600ms.

var _ = require('underscore');

var enabled = !! process.env['METEOR_PROFILE'];

var bucketTimes = {};

var spaces = function (x) {
  var s = '';
  for (var i = 0;  i < x;  ++i)
    s += ' ';
  return s;
};

var currentEntry = [];

var running = false;

var start = function () {
  bucketTimes = {};
  currentEntry = [];
  running = true;
};

var Profile = function (bucketName, f) {
  if (! enabled)
    return f;

  return function (/*arguments*/) {
    if (! running)
      return f.apply(this, arguments);

    var name;
    if (_.isFunction(bucketName))
      name = bucketName.apply(this, arguments);
    else
      name = bucketName;

    var parent = currentEntry;
    currentEntry = _.clone(currentEntry);
    currentEntry.push(name);
    var key = JSON.stringify(currentEntry);
    var start = process.hrtime();
    var err = null;
    try {
      return f.apply(this, arguments);
    }
    catch (e) {
      err = e;
    }
    finally {
      var elapsed = process.hrtime(start);
      bucketTimes[key] = (bucketTimes[key] || 0) +
        (elapsed[0] * 1000 + elapsed[1] / 1000000);
      currentEntry = parent;
    }

    if (err) throw err;
  };
};

var time = function (bucket, f) {
  return Profile(bucket, f)();
};

var entries = null;

var prefix = "| ";

var entryName = function (entry) {
  return _.last(entry);
};

var entryTime = function (entry) {
  return bucketTimes[JSON.stringify(entry)];
};

var isTopLevelEntry = function (entry) {
  return entry.length === 1;
};

var topLevelEntries = function () {
  return _.filter(entries, isTopLevelEntry);
};

var print = function (indent, text) {
  console.log(prefix + spaces(indent * 2) + text);
};

var startsWith = function (s1, s2) {
  return (s1.substr(0, s2.length) === s2);
};

var isChild = function (entry1, entry2) {
  return (entry2.length === entry1.length + 1 &&
          _.isEqual(entry1, entry2.slice(0, entry1.length)));
};

var children = function (entry1) {
  return _.filter(entries, function (entry2) {
    return isChild(entry1, entry2);
  });
}

var hasChildren = function (entry) {
  return children(entry).length !== 0;
};

var isLeaf = function (entry) {
  return ! hasChildren(entry);
};

var reportOnLeaf = function (level, entry) {
  print(
    level,
    _.last(entry) + ": " + entryTime(entry).toFixed(1));
};

var otherTime = function (entry) {
  var total = 0;
  _.each(children(entry), function (child) {
    total += bucketTimes[JSON.stringify(child)];
  });
  return entryTime(entry) - total;
};

var injectOtherTime = function (entry) {
  var name = "other " + entryName(entry);
  var other = _.clone(entry);
  other.push(name);
  bucketTimes[JSON.stringify(other)] = otherTime(entry);
  entries.push(other);
};

var reportOnParent = function (level, entry) {
  print(level, entryName(entry) + ": " + entryTime(entry).toFixed(1));
  _.each(children(entry), function (child) {
    reportOn(level + 1, child);
  });
};

var reportOn = function (level, entry) {
  if (hasChildren(entry))
    reportOnParent(level, entry);
  else
    reportOnLeaf(level, entry);
};

var reportHierarchy = function () {
  _.each(topLevelEntries(), function (entry) {
    reportOn(0, entry);
  });
};

var allLeafs = function () {
  return _.union(_.map(_.filter(entries, isLeaf), entryName));
};

var leafTotal = function (leafName) {
  var total = 0;
  _.each(
    _.filter(entries, function (entry) {
      return isLeaf(entry) && entryName(entry) === leafName;
    }),
    function (leaf) {
      total += entryTime(leaf);
    }
  );
  return total;
};

var reportTotals = function () {
  var totals = [];
  _.each(allLeafs(), function (leaf) {
    totals.push({name: leaf, time: leafTotal(leaf)});
  });
  totals.sort(function (a, b) {
    return a.time === b.time ? 0 : a.time > b.time ? -1 : 1;
  });
  var grandTotal = 0;
  _.each(totals, function (total) {
    print(0, total.name + ": " + total.time.toFixed(1));
    grandTotal += total.time;
  });
  print(0, "Total: " + grandTotal.toFixed(1));
};

var setupReport = function () {
  entries = _.map(_.keys(bucketTimes), JSON.parse);
  _.each(_.filter(entries, hasChildren), function (parent) {
    injectOtherTime(parent);
  });
};

var report = function () {
  if (! enabled)
    return;
  running = false;
  print(0, '');
  setupReport();
  reportHierarchy();
  print(0, '');
  reportTotals();
};

var run = function (bucketName, f) {
  start();
  try {
    return time(bucketName, f);
  }
  finally {
    report();
  }
};

Profile.time = time;
Profile.run = run;

exports.Profile = Profile;

<commit_msg>Make tools profiler Fibers-aware<commit_after>// Tiny profiler
//
// Enable by setting the environment variable `METEOR_PROFILE`.
//
// The main entry point is `Profile`, which wraps an existing function
// and returns a new function which, when called, calls the original
// function and profiles it.
//
// before:
//
//     foo: function (a) {
//       return a + this.b;
//     },
//
// after:
//
//     foo: Profile("foo", function (a) {
//       return a + this.b;
//     }),
//
// The advantage of this form is that it doesn't change the
// indentation of the wrapped code, which makes merging changes from
// other code branches easier.
//
// If profiling is disabled (if `METEOR_PROFILE` isn't set), `Profile`
// simply returns the original function.
//
// To run a profiling session and print the report, call `Profile.run`:
//
//     var createBundle = function () {
//       Profile.run("bundle", function () {
//         ...code to create the bundle which includes calls to `Profile`.
//       });
//     };
//
// Code is not profiled when called outside of a `Profile.run`, so the
// times in the report only include the time spent inside of the call
// to `Profile.run`.
//
// Sometimes you'll want to use a name for the profile bucket which
// depends on the arguments passed to the function or the value of
// `this`.  In this case you can pass a function for the bucket
// argument, which will be called to get the bucket name.
//
// before:
//     build: function (target) {
//       ... build target ...
//     },
//
// after:
//     build: Profile(
//       function (target) { return "build " + target; },
//       function (target) {
//         ... build target ...
//       }),
//
// But if it's easier, you can use `Profile.time` instead, which
// immediately calls the passed function with no arguments and
// profiles it, and returns what the function returns.
//
//     foo: function (a) {
//       var self = this;
//       return Profile.time("foo", function () {
//         return a + self.b;
//       });
//     },
//
//     build: function (target) {
//       var self = this;
//       self.doSomeSetup();
//       Profile.time("build " + target, function () {
//         ... build target ...
//       });
//       self.doSomeCleanup();
//     },
//
// The disadvantage is that you end up changing the indentation of the
// profiled code, which makes merging branches more painful.  But you
// can profile anywhere in the code; you don't have to just profile at
// function boundaries.
//
// Note profiling code will itself add a bit of execution time.
// If you profile in a tight loop and your total execution time is
// going up, you're probably starting to profile how long it takes to
// profile things :).
//
// If another profile (such as "compile js") is called while the first
// function is currently being profiled, this creates an entry like
// this:
//
//    build client : compile js
//
// which can continue to be nested, e.g.,
//
//    build client : compile js : read source files
//
// The total time reported for a bucket such as "build client" doesn't
// change regardless of whether it has child entries or not.  However,
// if an entry has child entries, it automatically gets an "other"
// entry:
//
//     build client: 400.0
//       compile js: 300.0
//         read source files: 20.0
//         other compile js: 280.0
//       other build client: 100.0
//
// The "other" entry reports how much time was spent in the "build
// client" entry not spent in the other child entries.
//
// The are two reports displayed: the hierarchical report and the
// leaf time report.  The hierarchical report looks like the example
// above and shows how much time was spent in each entry within its
// parent entry.
//
// The primary purpose of the hierarchical report is to be able to see
// where times are unaccounted for.  If you see a lot of time being
// spent in an "other" bucket, and you don't know what it is, you can
// add more profiling to dig deeper.
//
// The leaf time report shows the total time spent within leaf
// buckets.  For example, if if multiple steps have "read source
// files", the leaf time reports shows the total amount of time spent
// in "read source files" across all calls.
//
// Once you see in the hierarchical report that you have a good handle
// on accounting for most of the time, the leaf report shows you which
// buckets are the most expensive.
//
// By only including leaf buckets, the times in the leaf report are
// non-overlapping.  (The total of the times equals the elapsed time
// being profiled).
//
// For example, suppose "A" is profiled for a total time of 200ms, and
// that includes a call to "B" of 150ms:
//
//     B: 150
//     A (without B): 50
//
// and suppose there's another call to "A" which *doesn't* include a
// call to "B":
//
//     A: 300
//
// and there's a call to "B" directly:
//
//     B: 100
//
// All for a total time of 600ms.  In the hierarchical report, this
// looks like:
//
//     A: 500.0
//       B: 150.0
//       other A: 350.0
//     B: 100.0
//
// and in the leaf time report:
//
//     other A: 350.0
//     B: 250.0
//
// In both reports the grand total is 600ms.

var _ = require('underscore');
var Fiber = require('fibers');

var enabled = !! process.env['METEOR_PROFILE'];

var bucketTimes = {};

var spaces = function (x) {
  var s = '';
  for (var i = 0;  i < x;  ++i)
    s += '  ';
  return s;
};

var globalEntry = [];

var running = false;

var start = function () {
  bucketTimes = {};
  currentEntry = [];
  running = true;
};

var Profile = function (bucketName, f) {
  if (! enabled)
    return f;

  return function (/*arguments*/) {
    if (! running)
      return f.apply(this, arguments);

    var name;
    if (_.isFunction(bucketName))
      name = bucketName.apply(this, arguments);
    else
      name = bucketName;

    var currentEntry;
    if (Fiber.current) {
      currentEntry =
        Fiber.current.profilerEntry || (Fiber.current.profilerEntry = []);
    } else {
      currentEntry = globalEntry;
    }

    currentEntry.push(name);
    var key = JSON.stringify(currentEntry);
    var start = process.hrtime();
    var err = null;
    try {
      return f.apply(this, arguments);
    }
    catch (e) {
      err = e;
    }
    finally {
      var elapsed = process.hrtime(start);
      bucketTimes[key] = (bucketTimes[key] || 0) +
        (elapsed[0] * 1000 + elapsed[1] / 1000000);
      currentEntry.pop();
    }

    if (err) throw err;
  };
};

var time = function (bucket, f) {
  return Profile(bucket, f)();
};

var entries = null;

var prefix = "| ";

var entryName = function (entry) {
  return _.last(entry);
};

var entryTime = function (entry) {
  return bucketTimes[JSON.stringify(entry)];
};

var isTopLevelEntry = function (entry) {
  return entry.length === 1;
};

var topLevelEntries = function () {
  return _.filter(entries, isTopLevelEntry);
};

var print = function (indent, text) {
  console.log(prefix + spaces(indent * 2) + text);
};

var startsWith = function (s1, s2) {
  return (s1.substr(0, s2.length) === s2);
};

var isChild = function (entry1, entry2) {
  return (entry2.length === entry1.length + 1 &&
          _.isEqual(entry1, entry2.slice(0, entry1.length)));
};

var children = function (entry1) {
  return _.filter(entries, function (entry2) {
    return isChild(entry1, entry2);
  });
}

var hasChildren = function (entry) {
  return children(entry).length !== 0;
};

var isLeaf = function (entry) {
  return ! hasChildren(entry);
};

var reportOnLeaf = function (level, entry) {
  print(
    level,
    _.last(entry) + ": " + entryTime(entry).toFixed(1));
};

var otherTime = function (entry) {
  var total = 0;
  _.each(children(entry), function (child) {
    total += bucketTimes[JSON.stringify(child)];
  });
  return entryTime(entry) - total;
};

var injectOtherTime = function (entry) {
  var name = "other " + entryName(entry);
  var other = _.clone(entry);
  other.push(name);
  bucketTimes[JSON.stringify(other)] = otherTime(entry);
  entries.push(other);
};

var reportOnParent = function (level, entry) {
  print(level, entryName(entry) + ": " + entryTime(entry).toFixed(1));
  _.each(children(entry), function (child) {
    reportOn(level + 1, child);
  });
};

var reportOn = function (level, entry) {
  if (hasChildren(entry))
    reportOnParent(level, entry);
  else
    reportOnLeaf(level, entry);
};

var reportHierarchy = function () {
  _.each(topLevelEntries(), function (entry) {
    reportOn(0, entry);
  });
};

var allLeafs = function () {
  return _.union(_.map(_.filter(entries, isLeaf), entryName));
};

var leafTotal = function (leafName) {
  var total = 0;
  _.each(
    _.filter(entries, function (entry) {
      return isLeaf(entry) && entryName(entry) === leafName;
    }),
    function (leaf) {
      total += entryTime(leaf);
    }
  );
  return total;
};

var reportTotals = function () {
  var totals = [];
  _.each(allLeafs(), function (leaf) {
    totals.push({name: leaf, time: leafTotal(leaf)});
  });
  totals.sort(function (a, b) {
    return a.time === b.time ? 0 : a.time > b.time ? -1 : 1;
  });
  var grandTotal = 0;
  _.each(totals, function (total) {
    print(0, total.name + ": " + total.time.toFixed(1));
    grandTotal += total.time;
  });
  print(0, "Total: " + grandTotal.toFixed(1));
};

var setupReport = function () {
  entries = _.map(_.keys(bucketTimes), JSON.parse);
  _.each(_.filter(entries, hasChildren), function (parent) {
    injectOtherTime(parent);
  });
};

var report = function () {
  if (! enabled)
    return;
  running = false;
  print(0, '');
  setupReport();
  reportHierarchy();
  print(0, '');
  reportTotals();
};

var run = function (bucketName, f) {
  start();
  try {
    return time(bucketName, f);
  }
  finally {
    report();
  }
};

Profile.time = time;
Profile.run = run;

exports.Profile = Profile;

<|endoftext|>


<commit_before>/*
 * QueryBuilder.js
 * Written by Michael Albinson  11/22/16
 *
 * Functional interface for building queries and ensuring they are escaped and valid before
 * being sent to the database.
 */

"use strict";

var mysql = require('mysql');
var log = require('./../log');
var escaper = require('./QueryEscaper');
var lit = require('./../Literals.js');
var compare = require('./../Compare');

// the operators which are allowed to be used to query the database TODO: implement the 'in' and 'between' operators
const allowedOperators = ["like", "<=", ">=", ">", "<", "=", "!=", "<>"];

/** Creates an escaped "update" query provided the table and database object JSON
 *
 * @param table: The table that the row that's being updated is on
 * @param dbObject: The database object JSON to be broken down and escaped
 * @returns {string}: The escaped query string
 */
exports.update = function(table, dbObject) {
	if (!dbObject[lit.fields.ID])
		return log.warn("The field 'id' must be set in order to update a row");

	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to UPDATE");

	var base = "UPDATE " + table + " SET ";
	var dboBrokenDown = buildUpdateQueryString(dbObject, table);
	return base + dboBrokenDown + " WHERE id=" + exports.escapeID(dbObject[lit.fields.ID]) + ";";
};

/** Creates an escaped "insert" query provided the table and database object JSON
 *
 * @param table: The table that the row that's being updated is on
 * @param dbObject: The database object JSON to be broken down and escaped
 * @returns {string}: The escaped query string
 */
exports.insert = function(table, dbObject) {
	if (!dbObject[lit.fields.ID])
		return log.warn("The field 'id' must be set in order to insert a row");

	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to INSERT");

	var base = "INSERT INTO " + table + " ";
	var dboBrokenDown = buildInsertQueryString(dbObject, table);

	if (!dboBrokenDown)
		return undefined;

	return base + dboBrokenDown[0] + " VALUES " + dboBrokenDown[1] + ";";
};

/** Creates an escaped "select" query provided the table and database row's ID that only returns one row if the row exists,
 * none if it doesn't
 *
 * @param table: The table that the row that's being updated is on
 * @param rowId: The ID of the database row to get
 * @returns {string}: The escaped query string
 */
exports.get = function(table, rowId) {
	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to GET");

	return "SELECT * FROM " + table + " WHERE id=" + mysql.escape(rowId) + ";";
};

/** Creates an escaped "select" query provided the table and database object JSON
 *
 * @param table: The table that the row that's being updated is on
 * @param queryArray: the array of Queries to be escaped
 * @returns {string}: The escaped query string
 */
exports.query = function(table, queryArray) {
	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to QUERY");

	var base =  "SELECT * FROM " + table;

	if (!compare.isEmpty(queryArray))
		return base + " WHERE " + breakdownQueryObjects(queryArray);
	else
		return base;

};

/**
 * Breaks an array of query objects into a query string to be passed to the database
 *
 * @param qArr: {Array}[Query] An array of Query objects
 */
function breakdownQueryObjects(qArr) {
	var finalStr = '';
	for (var i in qArr) {
		if (qArr.hasOwnProperty(i)) {
            if (parseInt(i) === 0)
                finalStr += getAsSingleString(qArr[i], true);
            else
                finalStr += getAsSingleString(qArr[i], false);
        }
	}

	return finalStr;
}

/**
 * Deconstructs a single SQLQuery object into a partial query string
 *
 * @param obj {SQLQuery}
 * @param isFirst {boolean}
 * @return {string}
 */
function getAsSingleString(obj, isFirst) {
	var field, operator, value, joiner;
    var returnString = '';

    [field, operator, value, joiner] = obj.getQueryDataArray();
    if (!isFirst)
        returnString += ' ' + joiner + ' '; // the joiner has already been verified in the Query object

	returnString += field + " " + operator + " " + resolveObjectType(value);

    return returnString;
}

/** Creates an escaped "delete" query provided the table and database object JSON
 *
 * @param table {string}: The table that the row that's being updated is on
 * @param id {string}: the if of a database row to delete
 * @returns {string}: The escaped query string
 */
exports.delete = function(table, id) {
	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to DELETE");

	return "DELETE FROM " + table + " WHERE id=" + resolveObjectType(id) + ";";
};

/** Escapes the limit clause for queries
 *
 * @param limitNum {int}: The limit number that needs to be escaped
 * @returns {string}: The complete sanitized escape clause
 */
exports.escapeLimit = function(limitNum) {
	return "LIMIT " + resolveObjectType(limitNum);
};

/** Escapes the order by clause for queries
 *
 * @param table {string}: the table the field to order by should be on
 * @param field {string}: the field to order the query by
 * @param ascOrDesc {string]: indicates whether the field should be ordered by ascending or descending order. Must be the string 'asc' or 'desc' or their upper case versions
 * @returns {string|undefined}: the escaped order by clause or undefined if the field or operator passed in is invalid
 */
exports.escapeOrderBy = function(table, field, ascOrDesc) {
    var asc = "asc";
    var desc = "desc";
    if (!escaper.isValidField(table, field))
		return;

    if (ascOrDesc !== asc && ascOrDesc !== asc.toUpperCase() && ascOrDesc !== desc && ascOrDesc !== desc.toUpperCase())
    	return;

	return "ORDER BY " + field + " " + ascOrDesc;
};

/** Escapes the id passed in
 *
 * @param idToEscape {string}: the ID that needs to be escaped
 * @returns {string}: the escaped id
 */
exports.escapeID = function(idToEscape) {
	return mysql.escape(idToEscape);
};

/**
 *
 * @param obj {DBRow}
 * @param table {string}
 * @return {[string, string]}
 */
function buildInsertQueryString(obj, table) {
    var fields = "(";
    var values = "(";
    var numOfFields = Object.keys(obj).length;
    var itrs = 0;

    for (var prop in obj) {
        if (!obj.hasOwnProperty(prop))
            continue;

        if (!escaper.isValidField(table, prop))
            return log.warn('An invalid field "' + prop + '" for the table "' + table + '" was entered');

        fields += prop;
        values += resolveObjectType(obj[prop]);
        if (itrs < numOfFields-1) { // skip field if setting the ID is not allowed
            fields += ', ';
            values += ', ';
        }
        else {
			fields += ')';
			values += ')';
        }
        itrs++;
    }
    return [fields, values];
}

/**
 *
 * @param obj {DBRow}
 * @param table {string}
 * @return {string}
 */
function buildUpdateQueryString(obj, table) {
    var dbObjectString = "";
    var itrs = 0;
    var numOfFields = Object.keys(obj).length;

    for (var prop in obj) {
        if (!obj.hasOwnProperty(prop) || prop === 'id')
            continue;

        if (!escaper.isValidField(table, prop))
            return log.warn('An invalid field "' + prop + '" for the table "' + table + '" was entered');

        dbObjectString += prop + "=" + resolveObjectType(obj[prop]);

        if (itrs < numOfFields - 2)
            dbObjectString += ", ";

        itrs++;
    }
    return dbObjectString;
}

/** Resolves the object type of the object that is passed in. This is, perhaps, the core of the security of the website,
 * ensuring objects of unsecured types are rejected, causing queries to fail, and escaping queries to prevent SQL injection.
 *
 * @param resolveThis {*}: the object/primitive that needs to be resolved
 * @returns {*}: the escaped object, if it is of valid type. Returns undefined otherwise.
 */
function resolveObjectType(resolveThis) {
	var objectType = typeof resolveThis;

    if (resolveThis instanceof Date)
        return mysql.escape(resolveThis);

	if (objectType === lit.NUMBER)
		return resolveThis.toString();

	if (!isNaN(resolveThis))
		return resolveThis;

	if (objectType === lit.BOOLEAN && (resolveThis === lit.TRUE || resolveThis === lit.FALSE)) {
		if (resolveThis === true || resolveThis === lit.TRUE)
			return lit.ONE;
		else
			return lit.ZERO;
	}

	if (objectType === lit.STRING)
		return mysql.escape(resolveThis);

	if (objectType === lit.UNDEFINED)
		return null;

    if (objectType === lit.SYMBOL || objectType === lit.FUNCTION || objectType === lit.OBJECT)
        return log.warn("Objects with type '" + objectType + "' are not currently implemented, please pass a number, boolean or string");
}<commit_msg>Better Errors -- less silent<commit_after>/*
 * QueryBuilder.js
 * Written by Michael Albinson  11/22/16
 *
 * Functional interface for building queries and ensuring they are escaped and valid before
 * being sent to the database.
 */

"use strict";

var mysql = require('mysql');
var log = require('./../log');
var escaper = require('./QueryEscaper');
var lit = require('./../Literals.js');
var compare = require('./../Compare');

// the operators which are allowed to be used to query the database TODO: implement the 'in' and 'between' operators
const allowedOperators = ["like", "<=", ">=", ">", "<", "=", "!=", "<>"];

/** Creates an escaped "update" query provided the table and database object JSON
 *
 * @param table: The table that the row that's being updated is on
 * @param dbObject: The database object JSON to be broken down and escaped
 * @returns {string}: The escaped query string
 */
exports.update = function(table, dbObject) {
	if (!dbObject[lit.fields.ID])
		return log.warn("The field 'id' must be set in order to update a row");

	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to UPDATE");

	var base = "UPDATE " + table + " SET ";
	var dboBrokenDown = buildUpdateQueryString(dbObject, table);
	return base + dboBrokenDown + " WHERE id=" + exports.escapeID(dbObject[lit.fields.ID]) + ";";
};

/** Creates an escaped "insert" query provided the table and database object JSON
 *
 * @param table: The table that the row that's being updated is on
 * @param dbObject: The database object JSON to be broken down and escaped
 * @returns {string}: The escaped query string
 */
exports.insert = function(table, dbObject) {
	if (!dbObject[lit.fields.ID])
		return log.warn("The field 'id' must be set in order to insert a row");

	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to INSERT");

	var base = "INSERT INTO " + table + " ";
	var dboBrokenDown = buildInsertQueryString(dbObject, table);

	if (!dboBrokenDown)
		return undefined;

	return base + dboBrokenDown[0] + " VALUES " + dboBrokenDown[1] + ";";
};

/** Creates an escaped "select" query provided the table and database row's ID that only returns one row if the row exists,
 * none if it doesn't
 *
 * @param table: The table that the row that's being updated is on
 * @param rowId: The ID of the database row to get
 * @returns {string}: The escaped query string
 */
exports.get = function(table, rowId) {
	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to GET");

	return "SELECT * FROM " + table + " WHERE id=" + mysql.escape(rowId) + ";";
};

/** Creates an escaped "select" query provided the table and database object JSON
 *
 * @param table: The table that the row that's being updated is on
 * @param queryArray: the array of Queries to be escaped
 * @returns {string}: The escaped query string
 */
exports.query = function(table, queryArray) {
	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to QUERY");

	var base =  "SELECT * FROM " + table;

	if (!compare.isEmpty(queryArray))
		return base + " WHERE " + breakdownQueryObjects(queryArray);
	else
		return base;

};

/**
 * Breaks an array of query objects into a query string to be passed to the database
 *
 * @param qArr: {Array}[Query] An array of Query objects
 */
function breakdownQueryObjects(qArr) {
	var finalStr = '';
	for (var i in qArr) {
		if (qArr.hasOwnProperty(i)) {
            if (parseInt(i) === 0)
                finalStr += getAsSingleString(qArr[i], true);
            else
                finalStr += getAsSingleString(qArr[i], false);
        }
	}

	return finalStr;
}

/**
 * Deconstructs a single SQLQuery object into a partial query string
 *
 * @param obj {SQLQuery}
 * @param isFirst {boolean}
 * @return {string}
 */
function getAsSingleString(obj, isFirst) {
	var field, operator, value, joiner;
    var returnString = '';

    [field, operator, value, joiner] = obj.getQueryDataArray();
    if (!isFirst)
        returnString += ' ' + joiner + ' '; // the joiner has already been verified in the Query object

	returnString += field + " " + operator + " " + resolveObjectType(value);

    return returnString;
}

/** Creates an escaped "delete" query provided the table and database object JSON
 *
 * @param table {string}: The table that the row that's being updated is on
 * @param id {string}: the if of a database row to delete
 * @returns {string}: The escaped query string
 */
exports.delete = function(table, id) {
	if (!escaper.isValidTableName(table))
		return log.warn("Invalid table name used for call to DELETE");

	return "DELETE FROM " + table + " WHERE id=" + resolveObjectType(id) + ";";
};

/** Escapes the limit clause for queries
 *
 * @param limitNum {int}: The limit number that needs to be escaped
 * @returns {string}: The complete sanitized escape clause
 */
exports.escapeLimit = function(limitNum) {
	return "LIMIT " + resolveObjectType(limitNum);
};

/** Escapes the order by clause for queries
 *
 * @param table {string}: the table the field to order by should be on
 * @param field {string}: the field to order the query by
 * @param ascOrDesc {string]: indicates whether the field should be ordered by ascending or descending order. Must be the string 'asc' or 'desc' or their upper case versions
 * @returns {string|undefined}: the escaped order by clause or undefined if the field or operator passed in is invalid
 */
exports.escapeOrderBy = function(table, field, ascOrDesc) {
    var asc = "asc";
    var desc = "desc";
    if (!escaper.isValidField(table, field))
		return log.error('QueryBuilder Error: Field \'' + field + '\' is not valid for table \'' + table + '\'');

    if (ascOrDesc !== asc && ascOrDesc !== asc.toUpperCase() && ascOrDesc !== desc && ascOrDesc !== desc.toUpperCase())
    	return log.error('QueryBuilder Error: Order \'' + ascOrDesc + '\' is not one of "asc", "desc", "ASC", "DESC"');

	return "ORDER BY " + field + " " + ascOrDesc;
};

/** Escapes the id passed in
 *
 * @param idToEscape {string}: the ID that needs to be escaped
 * @returns {string}: the escaped id
 */
exports.escapeID = function(idToEscape) {
	return mysql.escape(idToEscape);
};

/**
 *
 * @param obj {DBRow}
 * @param table {string}
 * @return {[string, string]}
 */
function buildInsertQueryString(obj, table) {
    var fields = "(";
    var values = "(";
    var numOfFields = Object.keys(obj).length;
    var itrs = 0;

    for (var prop in obj) {
        if (!obj.hasOwnProperty(prop))
            continue;

        if (!escaper.isValidField(table, prop))
            return log.warn('An invalid field "' + prop + '" for the table "' + table + '" was entered');

        fields += prop;
        values += resolveObjectType(obj[prop]);
        if (itrs < numOfFields-1) { // skip field if setting the ID is not allowed
            fields += ', ';
            values += ', ';
        }
        else {
			fields += ')';
			values += ')';
        }
        itrs++;
    }
    return [fields, values];
}

/**
 *
 * @param obj {DBRow}
 * @param table {string}
 * @return {string}
 */
function buildUpdateQueryString(obj, table) {
    var dbObjectString = "";
    var itrs = 0;
    var numOfFields = Object.keys(obj).length;

    for (var prop in obj) {
        if (!obj.hasOwnProperty(prop) || prop === 'id')
            continue;

        if (!escaper.isValidField(table, prop))
            return log.warn('An invalid field "' + prop + '" for the table "' + table + '" was entered');

        dbObjectString += prop + "=" + resolveObjectType(obj[prop]);

        if (itrs < numOfFields - 2)
            dbObjectString += ", ";

        itrs++;
    }
    return dbObjectString;
}

/** Resolves the object type of the object that is passed in. This is, perhaps, the core of the security of the website,
 * ensuring objects of unsecured types are rejected, causing queries to fail, and escaping queries to prevent SQL injection.
 *
 * @param resolveThis {*}: the object/primitive that needs to be resolved
 * @returns {*}: the escaped object, if it is of valid type. Returns undefined otherwise.
 */
function resolveObjectType(resolveThis) {
	var objectType = typeof resolveThis;

    if (resolveThis instanceof Date)
        return mysql.escape(resolveThis);

	if (objectType === lit.NUMBER)
		return resolveThis.toString();

	if (!isNaN(resolveThis))
		return resolveThis;

	if (objectType === lit.BOOLEAN && (resolveThis === lit.TRUE || resolveThis === lit.FALSE)) {
		if (resolveThis === true || resolveThis === lit.TRUE)
			return lit.ONE;
		else
			return lit.ZERO;
	}

	if (objectType === lit.STRING)
		return mysql.escape(resolveThis);

	if (objectType === lit.UNDEFINED)
		return null;

    if (objectType === lit.SYMBOL || objectType === lit.FUNCTION || objectType === lit.OBJECT)
        return log.warn("Objects with type '" + objectType + "' are not currently implemented, please pass a number, boolean or string");
}<|endoftext|>