<commit_before>/**
 * Github API helper methods.
 *
 * @package  build
 * @author   Andrew Sliwinski <a@mozillafoundation.org>
 */

var async = require('async');
var lru = require('lru-cache');
var request = require('request');

/**
 * Constructor
 */
function Github(githubSecrets, cacheAge) {
  var _this = this;

  _this.client = githubSecrets.client;
  _this.secret = githubSecrets.secret;
  _this.token = githubSecrets.token;
  _this.host = 'https://api.github.com';
  _this.repo = '/repos/MozillaFoundation/plan';
  _this.cache = lru({
    max: 500,
    maxAge: cacheAge || 60 * 60 * 1000 // default: every hour
  });

  /**
   * Express middleware adapter. Attaches user information to the request if an
   * auth token exists within the session object.
   *
   * @param  {object}   req  Request
   * @param  {object}   res  Response
   * @param  {Function} next Callback
   */
  _this.middleware = function(req, res, next) {
    // If no token exists in the session, continue
    if (!req.session.token) {
      res.locals.user = null;
      return next();
    }

    // If token exists, fetch user from Github API & continue
    _this.getUserFromToken(req.session.token, function(err, user) {
      // Handle error state(s)
      if (err) {
        req.session.token = null;
        res.locals.user = null;
        return next();
      }

      // Attach user object to res.locals for template rendering
      res.locals.user = user;
      next();
    });
  };

  /**
   * Returns an object of arrays for "p1" and "p2" issues.
   *
   * @param  {array} issues  Issues from the Github api
   *
   * @return {object}
   */
  _this.sortIssuesByPriority = function(issues) {
    var result = {
      p1: [],
      p2: []
    };

    for (var i = 0; i < issues.length; i++) {
      var issue = issues[i];
      var isPriority = false;

      for (var l = 0; l < issue.labels.length; l++) {
        if (issue.labels[l].name === 'p1') {
          isPriority = true;
          break;
        }
      }

      var target = (isPriority) ? result.p1 : result.p2;
      target.push(issue);
    }
    return result;
  }
}

Github.prototype.githubRequest = function(options, callback) {
  var accessToken = this.token;
  var url = "https://api.github.com/" + options.query + "?access_token=" + accessToken + "&page=";
  var collection = [];
  var copy = this.cache.get(url);
  if (typeof copy !== 'undefined') {
    return callback(null, copy);
  }

  // Fetch deals with multiple pages.
  // The data this code deals with is small enough,
  // so just return all pages worth of data.
  var fetch = function(page) {
    request({
      url: url + page,
      headers: {
        "user-agent": "build.webmaker.org" // GitHub is a diva without a unique user agent
      }
    }, function(error, response, body) {
      var data = JSON.parse(body);
      if (data.length) {
        collection = collection.concat(data);
        // We have new data, keep going.
        fetch(++page);
      } else if (collection.length) {
        // Looks like we're done.
        this.cache.set(url, collection);
        callback(error, collection);
      } else if (data.message) {
        // Likely an error.
        callback(data);
      } else {
        // Likely dealing with non array data. We can stop.
        this.cache.set(url, data);
        callback(error, data);
      }
    }.bind(this));
  }.bind(this);

  fetch(0);
};

Github.prototype.githubJSON = function(fragment, callback) {
  var _this = this;
  var url = "https://api.github.com" + fragment;
  var copy = _this.cache.get(url);
  if (typeof copy !== 'undefined') {
    return callback(null, copy);
  }

  // Request from API
  request({
    method: 'GET',
    uri: url,
    headers: {
      'User-Agent': 'build.webmaker.org',
      Accept: 'application/vnd.github.v3+json',
      Authorization: 'token ' + _this.token
    },
    json: {}
  }, function(err, res, body) {
    if (err) return callback(err);
    // Set cache & return
    _this.cache.set(url, body);
    callback(err, body);
  });
}

/**
 * Returns a user object from the Github API based on the provided auth token.
 *
 * @param  {string}   token    OAuth token provided by Github
 * @param  {Function} callback
 */
Github.prototype.getUserFromToken = function(token, callback) {
  var _this = this;

  request({
    method: 'GET',
    uri: _this.host + '/user',
    headers: {
      'User-Agent': 'build.webmaker.org',
      Accept: 'application/vnd.github.v3+json',
      Authorization: 'token ' + token
    },
    json: {}
  }, function(err, res, body) {
    callback(err, body);
  });
};

/**
 * Creates a new issue using the provided token and body object.
 *
 * @param  {string}   token    OAuth token provided by Github
 * @param  {object}   body     Issue body
 * @param  {Function} callback
 */
Github.prototype.postIssueWithToken = function(token, body, callback) {
  var _this = this;

  request({
    method: 'POST',
    uri: _this.host + _this.repo + '/issues',
    headers: {
      'User-Agent': 'build.webmaker.org',
      Accept: 'application/vnd.github.v3+json',
      Authorization: 'token ' + token
    },
    json: body
  }, function(err, res, body) {
    callback(err, body);
  });
};

var mozillaRepos = "id.webmaker.org webmaker-curriculum snippets teach.webmaker.org goggles.webmaker.org webmaker-tests sawmill login.webmaker.org openbadges-badgekit webmaker-app api.webmaker.org popcorn.webmaker.org webmaker-mediasync webmaker.org webmaker-app-cordova webmaker-metrics nimble mozilla-opennews teach-api mozillafestival.org call-congress-net-neutrality thimble.webmaker.org advocacy.mozilla.org privacybadges webmaker-profile-2 call-congress build.webmaker.org webmaker-landing-pages webliteracymap events.webmaker.org badgekit-api openbadges-specification make-valet webmaker-auth webmaker-events-service webmaker-language-picker MakeAPI blog.webmaker.org webmaker-login-ux webmaker-desktop webmaker-app-publisher badges.mozilla.org lumberyard webmaker-download-locales webmaker-addons bsd-forms-and-wrappers popcorn-js hivelearningnetworks.org webmaker-firehose makeapi-client makerstrap webmaker-app-bot webmaker-screenshot react-i18n webmaker-kits-builder webmaker-app-guide".split(" ");
var foundationOrgs = ["MozillaFoundation", "MozillaScience"];

/**
 * Returns an array of milestones from the "plan" repo.
 *
 * @param  {string}   token    OAuth token provided by Github
 * @param  {Function} callback
 */
Github.prototype.getMilestones = function(callback) {
  this.githubJSON(this.repo + '/milestones', callback);
};

Github.prototype.getReposFromOrgs = function(orgs, callback) {
  async.concat(orgs, function(item, callback) {
    this.githubRequest({
      query: "orgs/" + item + "/repos"
    }, callback);
  }.bind(this), function(err, results) {
    callback(err, results);
  });
};

Github.prototype.getMozillaRepos = function(callback) {
  this.getReposFromOrgs(foundationOrgs, function(err, results) {
    if (err) {
      // Not sure what to do with errors, yet.
      callback(err);
    } else {
      var repoNames = [];
      results.forEach(function(repo) {
        repoNames.push(repo.full_name);
      });
      // Merge with static list of mozilla repos.
      callback(err, repoNames.concat(mozillaRepos.map(function(item) {
        return "mozilla/" + item;
      })));
    }
  });
};
Github.prototype.getUsersForOrgs = function(orgs, callback) {
  async.concat(orgs, function(item, callback) {
    this.githubRequest({
      query: "orgs/" + item + "/members"
    }, callback);
  }.bind(this), function(err, results) {
    var users = [];
    if (err) {
      callback(err);
    } else {
      // don't bother returning duplicates.
      results.forEach(function(user) {
        if (users.indexOf(user.login) === -1) {
          users.push(user.login);
        }
      });
      callback(err, users);
    }
  });
};

Github.prototype.getFoundationUsers = function(callback) {
  this.getUsersForOrgs(foundationOrgs, function(err, results) {
    callback(err, results);
  });
};

Github.prototype.getMilestonesForRepos = function(repos, callback) {
  async.concat(repos, function(item, callback) {
    var orgName = item.repo;
    var repoName = item.org;
    this.githubRequest({
      query: "repos/" + item + "/milestones"
    }, callback);
  }.bind(this), function(err, results) {
    var collection = [];
    if (err) {
      callback(err);
    } else {
      // don't bother returning duplicates.
      results.forEach(function(item) {
        if (collection.indexOf(item.title) === -1) {
          collection.push(item.title);
        }
      });
      callback(err, collection);
    }
  });
};

Github.prototype.getLabelsForRepos = function(repos, callback) {
  async.concat(repos, function(item, callback) {
    var orgName = item.repo;
    var repoName = item.org;
    this.githubRequest({
      query: "repos/" + item + "/labels"
    }, callback);
  }.bind(this), function(err, results) {
    var collection = [];
    if (err) {
      callback(err);
    } else {
      // don't bother returning duplicates.
      results.forEach(function(item) {
        if (collection.indexOf(item.name) === -1) {
          collection.push(item.name);
        }
      });
      callback(err, collection);
    }
  });
};

Github.prototype.getIssuesForMilestone = function(id, callback) {
  this.githubJSON(this.repo + '/issues?milestone=' + id, callback);
};

Github.prototype.thisMilestone = function(callback) {
  var _this = this;

  _this.getMilestones(function(err, milestones) {
    if (err) return callback(err);

    var milestone = milestones[0];
    // Look for the first milestone that is in the future
    for (var i = 0; i < milestones.length; i++) {
      milestone = milestones[i];
      if (new Date(milestone.due_on) > new Date()) {
        break;
      }
    }
    if (typeof milestone === 'undefined') return callback('404');
    _this.getIssuesForMilestone(milestone.number, function(err, result) {
      if (err) return callback(err);

      return callback(null, _this.sortIssuesByPriority(result));
    });
  });
};

Github.prototype.nextMilestone = function(callback) {
  var _this = this;

  _this.getMilestones(function(err, milestones) {
    if (err) return callback(err);

    var milestone = milestones[0];
    // Look for the first milestone that is in the future
    for (var i = 0; i < milestones.length; i++) {
      milestone = milestones[i];
      if (new Date(milestone.due_on) > new Date()) {
        // look for the next one.
        milestone = milestones[Math.min(i+1, milestones.length-1)]
        break;
      }
    }
    if (typeof milestone === 'undefined') return callback('404');
    _this.getIssuesForMilestone(milestone.number, function(err, result) {
      if (err) return callback(err);

      return callback(null, _this.sortIssuesByPriority(result));
    });
  });
};

Github.prototype.upcomingMilestones = function(callback) {
  var _this = this;

  // Get milestones
  _this.getMilestones(function(err, milestones) {
    if (err) return callback(err);

    // Limit results and map issues
    var set = milestones.slice(0, 6);
    async.map(set, function(milestone, callback) {
      _this.getIssuesForMilestone(milestone.number, function(err, issues) {
        if (err) return callback(err);

        milestone.issues = issues;
        callback(null, milestone);
      });
    }, callback);
  });
};

Github.prototype.getUserInfo = function(username, callback) {
  var _this = this;

  // Cache target
  var url = "https://api.github.com/users/" + username;
  var copy = _this.cache.get(url);
  if (typeof copy !== 'undefined') {
    return callback(null, copy);
  }

  // Request from API
  request({
    method: 'GET',
    uri: url,
    headers: {
      'User-Agent': 'build.webmaker.org',
      Accept: 'application/vnd.github.v3+json',
      Authorization: 'token ' + _this.token
    },
    json: {}
  }, function(err, res, body) {
    if (err) return callback(err);

    // Set cache & return
    _this.cache.set(url, body);
    callback(err, body);
  });
};

Github.prototype.teamMembers = function(team, callback) {
  _this = this;
  // Find the teams in the org
  // GET /orgs/:org/teams
  // find the id for team name "team"
  // get the members of the team
  // GET /teams/:id/members
  // For each member, get the user data (for names)
  this.githubJSON("/orgs/MozillaFoundation/teams", function(err, teamsblob) {
    // console.log(teamsblob);
    if (teamsblob) {
      teamsblob.forEach(function(teamblob) {
        console.log(teamblob.name.toLowerCase(), team.toLowerCase())
        if (teamblob.name.toLowerCase() == team.toLowerCase()) {
          var memberData = []
          // XXX this will break if we have > 100 people in a team.
          _this.githubJSON("/teams/"+teamblob.id+"/members?per_page=100", function(err, membersblob) {
            console.log("Got ", membersblob.length, "members" );
            async.map(membersblob, function(member, callback) {
              _this.githubJSON("/users/"+member.login, function(err, memberblob) {
                if (err) return callback(err);
                callback(null, memberblob);
              });
            }, callback);
          });
        }
      })
    }
    //callback('failure'); XXX
  });
};

// XXX REFACTOR THIS CACHING/TOKEN/JSON BUSINESS using githubJSON above

Github.prototype.myIssuesAssigned = function(callback) {
  this.githubJSON('/issues?filter=assigned&sort=updated&per_page=100', callback);
};

Github.prototype.myIssuesSubscribed = function(callback) {
  this.githubJSON('/issues?filter=subscribed&sort=updated&per_page=100', callback);
};

Github.prototype.myIssuesMentioned = function(callback) {
  this.githubJSON('/issues?filter=mentioned&sort=updated&per_page=100', callback);
};

Github.prototype.myIssuesCreated = function(callback) {
  this.githubJSON('/issues?filter=created&sort=updated&per_page=100', callback);
};

Github.prototype.search = function(q, sort, order, callback) {
  // Cache target
  var path = "?q="+encodeURIComponent(q) +
      "&sort="+encodeURIComponent(sort) +
      "&order="+encodeURIComponent(order);
  this.githubJSON("/search/issues/"+path, callback)
};
/**
 * Export
 */
module.exports = Github;
<commit_msg>debugging why heroku doesn't like my creds<commit_after>/**
 * Github API helper methods.
 *
 * @package  build
 * @author   Andrew Sliwinski <a@mozillafoundation.org>
 */

var async = require('async');
var lru = require('lru-cache');
var request = require('request');

/**
 * Constructor
 */
function Github(githubSecrets, cacheAge) {
  var _this = this;

  _this.client = githubSecrets.client;
  _this.secret = githubSecrets.secret;
  _this.token = githubSecrets.token;
  _this.host = 'https://api.github.com';
  _this.repo = '/repos/MozillaFoundation/plan';
  _this.cache = lru({
    max: 500,
    maxAge: cacheAge || 60 * 60 * 1000 // default: every hour
  });

  /**
   * Express middleware adapter. Attaches user information to the request if an
   * auth token exists within the session object.
   *
   * @param  {object}   req  Request
   * @param  {object}   res  Response
   * @param  {Function} next Callback
   */
  _this.middleware = function(req, res, next) {
    // If no token exists in the session, continue
    if (!req.session.token) {
      res.locals.user = null;
      return next();
    }

    // If token exists, fetch user from Github API & continue
    _this.getUserFromToken(req.session.token, function(err, user) {
      // Handle error state(s)
      if (err) {
        req.session.token = null;
        res.locals.user = null;
        return next();
      }

      // Attach user object to res.locals for template rendering
      res.locals.user = user;
      next();
    });
  };

  /**
   * Returns an object of arrays for "p1" and "p2" issues.
   *
   * @param  {array} issues  Issues from the Github api
   *
   * @return {object}
   */
  _this.sortIssuesByPriority = function(issues) {
    var result = {
      p1: [],
      p2: []
    };

    for (var i = 0; i < issues.length; i++) {
      var issue = issues[i];
      var isPriority = false;

      for (var l = 0; l < issue.labels.length; l++) {
        if (issue.labels[l].name === 'p1') {
          isPriority = true;
          break;
        }
      }

      var target = (isPriority) ? result.p1 : result.p2;
      target.push(issue);
    }
    return result;
  }
}

Github.prototype.githubRequest = function(options, callback) {
  var accessToken = this.token;
  var url = "https://api.github.com/" + options.query + "?page=";
  var collection = [];
  var copy = this.cache.get(url);
  if (typeof copy !== 'undefined') {
    return callback(null, copy);
  }

  // Fetch deals with multiple pages.
  // The data this code deals with is small enough,
  // so just return all pages worth of data.
  var fetch = function(page) {
    request({
      method: 'GET',
      uri: url + page,
      json: {},
      headers: {
        'User-Agent': 'build.webmaker.org',
        Accept: 'application/vnd.github.v3+json',
        Authorization: 'token ' + accessToken
      }
    }, function(error, response, body) {
      var data = JSON.parse(body);
      if (data.length) {
        collection = collection.concat(data);
        // We have new data, keep going.
        fetch(++page);
      } else if (collection.length) {
        // Looks like we're done.
        this.cache.set(url, collection);
        callback(error, collection);
      } else if (data.message) {
        // Likely an error.
        callback(data);
      } else {
        // Likely dealing with non array data. We can stop.
        this.cache.set(url, data);
        callback(error, data);
      }
    }.bind(this));
  }.bind(this);

  fetch(0);
};

Github.prototype.githubJSON = function(fragment, callback) {
  var _this = this;
  var url = "https://api.github.com" + fragment;
  var copy = _this.cache.get(url);
  if (typeof copy !== 'undefined') {
    return callback(null, copy);
  }

  // Request from API
  request({
    method: 'GET',
    uri: url,
    headers: {
      'User-Agent': 'build.webmaker.org',
      Accept: 'application/vnd.github.v3+json',
      Authorization: 'token ' + _this.token
    },
    json: {}
  }, function(err, res, body) {
    if (err) return callback(err);
    // Set cache & return
    _this.cache.set(url, body);
    callback(err, body);
  });
}

/**
 * Returns a user object from the Github API based on the provided auth token.
 *
 * @param  {string}   token    OAuth token provided by Github
 * @param  {Function} callback
 */
Github.prototype.getUserFromToken = function(token, callback) {
  var _this = this;

  request({
    method: 'GET',
    uri: _this.host + '/user',
    headers: {
      'User-Agent': 'build.webmaker.org',
      Accept: 'application/vnd.github.v3+json',
      Authorization: 'token ' + token
    },
    json: {}
  }, function(err, res, body) {
    callback(err, body);
  });
};

/**
 * Creates a new issue using the provided token and body object.
 *
 * @param  {string}   token    OAuth token provided by Github
 * @param  {object}   body     Issue body
 * @param  {Function} callback
 */
Github.prototype.postIssueWithToken = function(token, body, callback) {
  var _this = this;

  request({
    method: 'POST',
    uri: _this.host + _this.repo + '/issues',
    headers: {
      'User-Agent': 'build.webmaker.org',
      Accept: 'application/vnd.github.v3+json',
      Authorization: 'token ' + token
    },
    json: body
  }, function(err, res, body) {
    callback(err, body);
  });
};

var mozillaRepos = "id.webmaker.org webmaker-curriculum snippets teach.webmaker.org goggles.webmaker.org webmaker-tests sawmill login.webmaker.org openbadges-badgekit webmaker-app api.webmaker.org popcorn.webmaker.org webmaker-mediasync webmaker.org webmaker-app-cordova webmaker-metrics nimble mozilla-opennews teach-api mozillafestival.org call-congress-net-neutrality thimble.webmaker.org advocacy.mozilla.org privacybadges webmaker-profile-2 call-congress build.webmaker.org webmaker-landing-pages webliteracymap events.webmaker.org badgekit-api openbadges-specification make-valet webmaker-auth webmaker-events-service webmaker-language-picker MakeAPI blog.webmaker.org webmaker-login-ux webmaker-desktop webmaker-app-publisher badges.mozilla.org lumberyard webmaker-download-locales webmaker-addons bsd-forms-and-wrappers popcorn-js hivelearningnetworks.org webmaker-firehose makeapi-client makerstrap webmaker-app-bot webmaker-screenshot react-i18n webmaker-kits-builder webmaker-app-guide".split(" ");
var foundationOrgs = ["MozillaFoundation", "MozillaScience"];

/**
 * Returns an array of milestones from the "plan" repo.
 *
 * @param  {string}   token    OAuth token provided by Github
 * @param  {Function} callback
 */
Github.prototype.getMilestones = function(callback) {
  this.githubJSON(this.repo + '/milestones', callback);
};

Github.prototype.getReposFromOrgs = function(orgs, callback) {
  async.concat(orgs, function(item, callback) {
    this.githubRequest({
      query: "orgs/" + item + "/repos"
    }, callback);
  }.bind(this), function(err, results) {
    callback(err, results);
  });
};

Github.prototype.getMozillaRepos = function(callback) {
  this.getReposFromOrgs(foundationOrgs, function(err, results) {
    if (err) {
      // Not sure what to do with errors, yet.
      callback(err);
    } else {
      var repoNames = [];
      results.forEach(function(repo) {
        repoNames.push(repo.full_name);
      });
      // Merge with static list of mozilla repos.
      callback(err, repoNames.concat(mozillaRepos.map(function(item) {
        return "mozilla/" + item;
      })));
    }
  });
};
Github.prototype.getUsersForOrgs = function(orgs, callback) {
  async.concat(orgs, function(item, callback) {
    this.githubRequest({
      query: "orgs/" + item + "/members"
    }, callback);
  }.bind(this), function(err, results) {
    var users = [];
    if (err) {
      callback(err);
    } else {
      // don't bother returning duplicates.
      results.forEach(function(user) {
        if (users.indexOf(user.login) === -1) {
          users.push(user.login);
        }
      });
      callback(err, users);
    }
  });
};

Github.prototype.getFoundationUsers = function(callback) {
  this.getUsersForOrgs(foundationOrgs, function(err, results) {
    callback(err, results);
  });
};

Github.prototype.getMilestonesForRepos = function(repos, callback) {
  async.concat(repos, function(item, callback) {
    var orgName = item.repo;
    var repoName = item.org;
    this.githubRequest({
      query: "repos/" + item + "/milestones"
    }, callback);
  }.bind(this), function(err, results) {
    var collection = [];
    if (err) {
      callback(err);
    } else {
      // don't bother returning duplicates.
      results.forEach(function(item) {
        if (collection.indexOf(item.title) === -1) {
          collection.push(item.title);
        }
      });
      callback(err, collection);
    }
  });
};

Github.prototype.getLabelsForRepos = function(repos, callback) {
  async.concat(repos, function(item, callback) {
    var orgName = item.repo;
    var repoName = item.org;
    this.githubRequest({
      query: "repos/" + item + "/labels"
    }, callback);
  }.bind(this), function(err, results) {
    var collection = [];
    if (err) {
      callback(err);
    } else {
      // don't bother returning duplicates.
      results.forEach(function(item) {
        if (collection.indexOf(item.name) === -1) {
          collection.push(item.name);
        }
      });
      callback(err, collection);
    }
  });
};

Github.prototype.getIssuesForMilestone = function(id, callback) {
  this.githubJSON(this.repo + '/issues?milestone=' + id, callback);
};

Github.prototype.thisMilestone = function(callback) {
  var _this = this;

  _this.getMilestones(function(err, milestones) {
    if (err) return callback(err);

    var milestone = milestones[0];
    // Look for the first milestone that is in the future
    for (var i = 0; i < milestones.length; i++) {
      milestone = milestones[i];
      if (new Date(milestone.due_on) > new Date()) {
        break;
      }
    }
    if (typeof milestone === 'undefined') return callback('404');
    _this.getIssuesForMilestone(milestone.number, function(err, result) {
      if (err) return callback(err);

      return callback(null, _this.sortIssuesByPriority(result));
    });
  });
};

Github.prototype.nextMilestone = function(callback) {
  var _this = this;

  _this.getMilestones(function(err, milestones) {
    if (err) return callback(err);

    var milestone = milestones[0];
    // Look for the first milestone that is in the future
    for (var i = 0; i < milestones.length; i++) {
      milestone = milestones[i];
      if (new Date(milestone.due_on) > new Date()) {
        // look for the next one.
        milestone = milestones[Math.min(i+1, milestones.length-1)]
        break;
      }
    }
    if (typeof milestone === 'undefined') return callback('404');
    _this.getIssuesForMilestone(milestone.number, function(err, result) {
      if (err) return callback(err);

      return callback(null, _this.sortIssuesByPriority(result));
    });
  });
};

Github.prototype.upcomingMilestones = function(callback) {
  var _this = this;

  // Get milestones
  _this.getMilestones(function(err, milestones) {
    if (err) return callback(err);

    // Limit results and map issues
    var set = milestones.slice(0, 6);
    async.map(set, function(milestone, callback) {
      _this.getIssuesForMilestone(milestone.number, function(err, issues) {
        if (err) return callback(err);

        milestone.issues = issues;
        callback(null, milestone);
      });
    }, callback);
  });
};

Github.prototype.getUserInfo = function(username, callback) {
  var _this = this;

  // Cache target
  var url = "https://api.github.com/users/" + username;
  var copy = _this.cache.get(url);
  if (typeof copy !== 'undefined') {
    return callback(null, copy);
  }

  // Request from API
  request({
    method: 'GET',
    uri: url,
    headers: {
      'User-Agent': 'build.webmaker.org',
      Accept: 'application/vnd.github.v3+json',
      Authorization: 'token ' + _this.token
    },
    json: {}
  }, function(err, res, body) {
    if (err) return callback(err);

    // Set cache & return
    _this.cache.set(url, body);
    callback(err, body);
  });
};

Github.prototype.teamMembers = function(team, callback) {
  _this = this;
  // Find the teams in the org
  // GET /orgs/:org/teams
  // find the id for team name "team"
  // get the members of the team
  // GET /teams/:id/members
  // For each member, get the user data (for names)
  this.githubJSON("/orgs/MozillaFoundation/teams", function(err, teamsblob) {
    // console.log(teamsblob);
    if (teamsblob) {
      teamsblob.forEach(function(teamblob) {
        console.log(teamblob.name.toLowerCase(), team.toLowerCase())
        if (teamblob.name.toLowerCase() == team.toLowerCase()) {
          var memberData = []
          // XXX this will break if we have > 100 people in a team.
          _this.githubJSON("/teams/"+teamblob.id+"/members?per_page=100", function(err, membersblob) {
            console.log("Got ", membersblob.length, "members" );
            async.map(membersblob, function(member, callback) {
              _this.githubJSON("/users/"+member.login, function(err, memberblob) {
                if (err) return callback(err);
                callback(null, memberblob);
              });
            }, callback);
          });
        }
      })
    }
    //callback('failure'); XXX
  });
};

// XXX REFACTOR THIS CACHING/TOKEN/JSON BUSINESS using githubJSON above

Github.prototype.myIssuesAssigned = function(callback) {
  this.githubJSON('/issues?filter=assigned&sort=updated&per_page=100', callback);
};

Github.prototype.myIssuesSubscribed = function(callback) {
  this.githubJSON('/issues?filter=subscribed&sort=updated&per_page=100', callback);
};

Github.prototype.myIssuesMentioned = function(callback) {
  this.githubJSON('/issues?filter=mentioned&sort=updated&per_page=100', callback);
};

Github.prototype.myIssuesCreated = function(callback) {
  this.githubJSON('/issues?filter=created&sort=updated&per_page=100', callback);
};

Github.prototype.search = function(q, sort, order, callback) {
  // Cache target
  var path = "?q="+encodeURIComponent(q) +
      "&sort="+encodeURIComponent(sort) +
      "&order="+encodeURIComponent(order);
  this.githubJSON("/search/issues/"+path, callback)
};
/**
 * Export
 */
module.exports = Github;
<|endoftext|>


<commit_before>// A bunch of helper functions (mainly for Jade)
var moment = require("moment");
// The site I found this on was British, can you tell?
var words = ["anal", "anus", "arse", "ass", "ballsack", "balls", "bastard", "bitch", "biatch", "bloody", "blowjob", "blow job", "bollock", "bollok", "boner", "boob", "bugger", "bum", "butt", "buttplug", "clitoris", "cock", "coon", "crap", "cunt", "damn", "dick", "dildo", "dyke", "fag", "feck", "fellate", "fellatio", "felching", "fuck", "f u c k", "fudgepacker", "fudge packer", "flange", "Goddamn", "God damn", "hell", "homo", "jerk", "jizz", "knobend", "knob end", "labia", "lmao", "lmfao", "muff", "nigger", "nigga", "omg", "penis", "piss", "poop", "prick", "pube", "pussy", "queer", "scrotum", "sex", "shit", "s hit", "sh1t", "slut", "smegma", "spunk", "tit", "tosser", "turd", "twat", "vagina", "wank", "whore", "wtf"];

module.exports = {
  getDayScheduleInfo: function(array){
    var mom = moment();
    var dayStart = moment("08:40 AM", "hh:mm A");
    var dayEnd = moment("02:50 PM", "hh:mm A");

      var day = "09/24/15";
      mom = moment(day+" 12:30 PM", "MM/DD/YY hh:mm A");
      dayStart = moment(day+" 08:40 AM", "MM/DD/YY hh:mm A");
      dayEnd = moment(day+" 02:50 PM", "MM/DD/YY hh:mm A");

    var todays = array.filter(function(period) {
      if(moment(period.date).isSame(moment(mom).startOf('day'))){
        //console.log(period.className+":\n"+moment(period.startTime).toDate()+"\n"+moment(period.endTime).toDate())
        return true;
      }
      return false;
    });
    todays = (todays.length > 0 ? todays : false);

    var now = false;
    var next = false;

    var justEnded = false;
    var justStarted = false;

    if(mom.isBetween(dayStart, dayEnd)){
      // RIGHT NOW IS IN A SCHOOL DAY
      //console.log("IN SCHOOL DAY");

      if(todays !== false){
        // THERE ARE CLASSES SCHEDULE FOR TODAY

        // Free periods are not recorded, so find the holes and fill them
        var newTodays = [];
        var lastPeriod = {};
        todays.forEach(function(period) {
          var cur = period;

          if(moment(period.startTime).isSame(lastPeriod.endTime) == false && todays.indexOf(period) > 0){
            newTodays.push({
              date: period.date,
              room: "Anywhere",
              startTime: lastPeriod.endTime,
              endTime: period.startTime,
              className: "Unstructured Time"
            });
          }

          lastPeriod = period;
          newTodays.push(cur);
        });

        if(moment(todays[todays.length-1].endTime).isSame(dayEnd) == false){
          newTodays.push({
            date: lastPeriod.date,
            room: "Anywhere",
            startTime: lastPeriod.endTime,
            endTime: dayEnd.toDate(),
            className: "Unstructured Time"
          });
        }
        // I am a genius for this ^^^

        todays = newTodays; // Free periods are now in array

        // Loop through today's classes to find the currently ongoing
        now = todays.filter(function(period) {
          if(mom.isBetween(period.startTime, period.endTime)){
            return true;
          }
          return false;
        });
        if(now.length == 1) now = now[0];
        // If there is no such period (and it is during the school day)

        // Try to find a class that just ended
        var cur = mom;
        var found = todays.filter(function(period) {
          if(cur.isSame(period.endTime)){
            return true;
          }
          return false;
        });
        if(found.length == 1){ justEnded = found[0];}else{justEnded = false;}
        // If none found set to false

        // Try to find a class that just started
        var found = todays.filter(function(period) {
          if(cur.isSame(period.startTime)){
            return true;
          }
          return false;
        });
        if(found.length == 1){ justStarted = found[0];}else{justStarted = false;}
        // If none set to false

        // If a class jas just ended and another jas just started, you are in between adjacent classes
        if(justStarted !== false && justEnded !== false){
          now = "between";
        }

        // Get the next class
        if(now !== "between")
          next = ((todays.length-1 > todays.indexOf(now)) ? todays[todays.indexOf(now)+1] : false);
      }
    }else{
      now = false;
      // NOT IN SCHOOL
      //console.log("OUT OF SCHOOL DAY");
    }

    //console.log(now);
    //console.log(next);

    return {
      todays: todays,
      nowClass: now,
      nextClass: next,
      justEnded: justEnded,
      justStarted: justStarted
    }
  },
  pluralize: function(num, non, pluralized) {
    if(num == 0 || num > 1)
      return pluralized;
    else
      return non;
  },
  banned: words,
  filter: function(str){
    words.forEach(function(word) {
      var re = new RegExp(word, 'g');
      str = str.replace(re, 'Vode');
    });
    return str;
  },
  clean: function(word) {
    words.forEach(function(word) {
      if(str.includes(word)){
        return false;
      }
    });
    return true;
  },
  isPage : function(word, other) {
    return (word.indexOf(other) > -1 ? "active" : "");
  },
  isReach: function(department) {
    return (department.indexOf("REACH") > -1);
  },
  advisor: function(adv) {
    for (var key in adv.teacher) {
      console.log(key, adv.teacher[key]);
    }
    return adv.teacher.toJSON();
  },
  isShort: function(p, n){
    console.log(p || n);
    if(p || n)
      return "";
    else
      return "short";
  },
  fromNow: function(date) {
    var today = moment().startOf('day');
    if(date.isSame(today)){
      return "today";
    }else{
      return date.from(today);
    }
  }

}
<commit_msg>Removed fake test date again<commit_after>// A bunch of helper functions (mainly for Jade)
var moment = require("moment");
// The site I found this on was British, can you tell?
var words = ["anal", "anus", "arse", "ass", "ballsack", "balls", "bastard", "bitch", "biatch", "bloody", "blowjob", "blow job", "bollock", "bollok", "boner", "boob", "bugger", "bum", "butt", "buttplug", "clitoris", "cock", "coon", "crap", "cunt", "damn", "dick", "dildo", "dyke", "fag", "feck", "fellate", "fellatio", "felching", "fuck", "f u c k", "fudgepacker", "fudge packer", "flange", "Goddamn", "God damn", "hell", "homo", "jerk", "jizz", "knobend", "knob end", "labia", "lmao", "lmfao", "muff", "nigger", "nigga", "omg", "penis", "piss", "poop", "prick", "pube", "pussy", "queer", "scrotum", "sex", "shit", "s hit", "sh1t", "slut", "smegma", "spunk", "tit", "tosser", "turd", "twat", "vagina", "wank", "whore", "wtf"];

module.exports = {
  getDayScheduleInfo: function(array){
    var mom = moment();
    var dayStart = moment("08:40 AM", "hh:mm A");
    var dayEnd = moment("02:50 PM", "hh:mm A");

    /*
      var day = "09/24/15";
      mom = moment(day+" 12:30 PM", "MM/DD/YY hh:mm A");
      dayStart = moment(day+" 08:40 AM", "MM/DD/YY hh:mm A");
      dayEnd = moment(day+" 02:50 PM", "MM/DD/YY hh:mm A");
    */

    var todays = array.filter(function(period) {
      if(moment(period.date).isSame(moment(mom).startOf('day'))){
        //console.log(period.className+":\n"+moment(period.startTime).toDate()+"\n"+moment(period.endTime).toDate())
        return true;
      }
      return false;
    });
    todays = (todays.length > 0 ? todays : false);

    var now = false;
    var next = false;

    var justEnded = false;
    var justStarted = false;

    if(mom.isBetween(dayStart, dayEnd)){
      // RIGHT NOW IS IN A SCHOOL DAY
      //console.log("IN SCHOOL DAY");

      if(todays !== false){
        // THERE ARE CLASSES SCHEDULE FOR TODAY

        // Free periods are not recorded, so find the holes and fill them
        var newTodays = [];
        var lastPeriod = {};
        todays.forEach(function(period) {
          var cur = period;

          if(moment(period.startTime).isSame(lastPeriod.endTime) == false && todays.indexOf(period) > 0){
            newTodays.push({
              date: period.date,
              room: "Anywhere",
              startTime: lastPeriod.endTime,
              endTime: period.startTime,
              className: "Unstructured Time"
            });
          }

          lastPeriod = period;
          newTodays.push(cur);
        });

        if(moment(todays[todays.length-1].endTime).isSame(dayEnd) == false){
          newTodays.push({
            date: lastPeriod.date,
            room: "Anywhere",
            startTime: lastPeriod.endTime,
            endTime: dayEnd.toDate(),
            className: "Unstructured Time"
          });
        }
        // I am a genius for this ^^^

        todays = newTodays; // Free periods are now in array

        // Loop through today's classes to find the currently ongoing
        now = todays.filter(function(period) {
          if(mom.isBetween(period.startTime, period.endTime)){
            return true;
          }
          return false;
        });
        if(now.length == 1) now = now[0];
        // If there is no such period (and it is during the school day)

        // Try to find a class that just ended
        var cur = mom;
        var found = todays.filter(function(period) {
          if(cur.isSame(period.endTime)){
            return true;
          }
          return false;
        });
        if(found.length == 1){ justEnded = found[0];}else{justEnded = false;}
        // If none found set to false

        // Try to find a class that just started
        var found = todays.filter(function(period) {
          if(cur.isSame(period.startTime)){
            return true;
          }
          return false;
        });
        if(found.length == 1){ justStarted = found[0];}else{justStarted = false;}
        // If none set to false

        // If a class jas just ended and another jas just started, you are in between adjacent classes
        if(justStarted !== false && justEnded !== false){
          now = "between";
        }

        // Get the next class
        if(now !== "between")
          next = ((todays.length-1 > todays.indexOf(now)) ? todays[todays.indexOf(now)+1] : false);
      }
    }else{
      now = false;
      // NOT IN SCHOOL
      //console.log("OUT OF SCHOOL DAY");
    }

    //console.log(now);
    //console.log(next);

    return {
      todays: todays,
      nowClass: now,
      nextClass: next,
      justEnded: justEnded,
      justStarted: justStarted
    }
  },
  pluralize: function(num, non, pluralized) {
    if(num == 0 || num > 1)
      return pluralized;
    else
      return non;
  },
  banned: words,
  filter: function(str){
    words.forEach(function(word) {
      var re = new RegExp(word, 'g');
      str = str.replace(re, 'Vode');
    });
    return str;
  },
  clean: function(word) {
    words.forEach(function(word) {
      if(str.includes(word)){
        return false;
      }
    });
    return true;
  },
  isPage : function(word, other) {
    return (word.indexOf(other) > -1 ? "active" : "");
  },
  isReach: function(department) {
    return (department.indexOf("REACH") > -1);
  },
  advisor: function(adv) {
    for (var key in adv.teacher) {
      console.log(key, adv.teacher[key]);
    }
    return adv.teacher.toJSON();
  },
  isShort: function(p, n){
    console.log(p || n);
    if(p || n)
      return "";
    else
      return "short";
  },
  fromNow: function(date) {
    var today = moment().startOf('day');
    if(date.isSame(today)){
      return "today";
    }else{
      return date.from(today);
    }
  }

}
<|endoftext|>


<commit_before>import invariant from 'invariant';
import isEqual from 'lodash/isEqual';
import Relay from 'react-relay';
import generateRQLFieldAlias from 'react-relay/lib/generateRQLFieldAlias';

import getRouteQueries from './utils/getRouteQueries';
import mergeRouteParams from './utils/mergeRouteParams';

const DEFAULT_KEY = '@@default';

export default class QueryAggregator {
  constructor(routerProps) {
    // We need to use a map to track route indices instead of throwing them on
    // the route itself with a Symbol to ensure that, when rendering on the
    // server, each request generates route indices independently.
    this.routeIndices = new WeakMap();
    this.lastRouteIndex = 0;

    this.queryConfig = null;
    this.fragmentSpecs = null;

    this.props = {};
    this.readyState = null;

    this.updateQueryConfig(routerProps);
  }

  updateQueryConfig(routerProps) {
    const { routes, components } = routerProps;

    const queryConfig = {
      name: null,
      queries: {},
      params: {},
    };
    const fragmentSpecs = {};

    routes.forEach((route, i) => {
      // We need to merge in the route params regardless of whether the route
      // actually has queries, in case its children depend on its path params.
      queryConfig.params = mergeRouteParams(
        queryConfig.params, route, routerProps
      );

      const routeQueries = getRouteQueries(route, routerProps);
      if (!routeQueries) {
        return;
      }

      const routeComponent = components[i];

      let componentMap;
      let queryMap;
      if (typeof routeComponent === 'object') {
        componentMap = routeComponent;
        queryMap = routeQueries;
      } else {
        componentMap = { [DEFAULT_KEY]: routeComponent };
        queryMap = { [DEFAULT_KEY]: routeQueries };
      }

      Object.keys(componentMap).forEach(key => {
        const component = componentMap[key];
        const queries = queryMap[key];

        if (!queries) {
          return;
        }

        // In principle not all container component routes have to specify
        // queries, because some of them might somehow receive fragments from
        // their parents, but it would definitely be wrong to specify queries
        // for a component that isn't a container.
        invariant(
          Relay.isContainer(component),
          'relay-router-relay: Route with queries specifies component `%s` ' +
          'that is not a Relay container.',
          component && (component.displayName || component.name)
        );

        Object.keys(queries).forEach(queryName => {
          const query = queries[queryName];
          const uniqueQueryName =
            this.getUniqueQueryName(route, key, queryName);

          // Relay depends on the argument count of the query function, so try
          // to preserve it as well as possible.
          let wrappedQuery;
          if (query.length === 0) {
            // Relay doesn't like using the exact same query in multiple
            // places, so wrap it to prevent that when sharing queries between
            // routes.
            wrappedQuery = () => query();
          } else {
            // When not using the shorthand, we can control the injected
            // params, so restrict them to just the ones for the current route
            // and its ancestors.
            const paramsForRoute = queryConfig.params;

            // We need the query function to have > 0 arguments to hit the code
            // path for non-shorthand queries.
            /* eslint-disable no-unused-vars */
            wrappedQuery = _ => query(component, paramsForRoute);
            /* eslint-enable */
          }

          queryConfig.queries[uniqueQueryName] = wrappedQuery;
          fragmentSpecs[uniqueQueryName] = { component, queryName };
        });
      });
    });

    queryConfig.name = generateRQLFieldAlias(
      `ReactRouterRelay.${Object.keys(queryConfig.queries).join('-')}`,
    );

    // RootContainer uses referential equality to check for route change, so
    // replace the route object entirely.
    this.queryConfig = queryConfig;
    this.fragmentSpecs = fragmentSpecs;
  }

  getUniqueQueryName(route, key, queryName) {
    // There might be some edge case here where the query changes but the route
    // object does not, in which case we'll keep using the old unique name.
    // Anybody who does that deserves whatever they get, though.

    // Prefer an explicit route name if specified.
    if (route.name) {
      // The slightly different template here ensures that we can't have
      // collisions with the below template.
      return `$_${route.name}_${key}_${queryName}`;
    }

    // Otherwise, use referential equality on the route name to generate a
    // unique index.
    let routeIndex = this.routeIndices.get(route);
    if (routeIndex === undefined) {
      routeIndex = ++this.lastRouteIndex;
      this.routeIndices.set(route, routeIndex);
    }

    return `$$_route[${routeIndex}]_${key}_${queryName}`;
  }

  setRenderArgs({ props, ...readyState }) {
    if (props) {
      this.props = props;
    }

    this.readyState = readyState;
  }

  getRenderArgs(route, key, queries, params) {
    return {
      ...this.readyState,
      props: this.getProps(route, key, queries, params),
    };
  }

  getProps(route, key = DEFAULT_KEY, queries, params) {
    // Check that the subset of parameters used for this route match those used
    // for the fetched data.
    for (const paramName of Object.keys(params)) {
      if (!isEqual(this.props[paramName], params[paramName])) {
        return null;
      }
    }

    const props = {};
    for (const queryName of Object.keys(queries)) {
      const uniqueQueryName = this.getUniqueQueryName(route, key, queryName);

      const value = this.props[uniqueQueryName];
      if (!value) {
        return null;
      }

      props[queryName] = value;
    }

    // Only return the props for the route if the query config params match and
    // all requested props are available. Otherwise, by assumption, the ready
    // state will have the correct "not ready" state.
    return props;
  }

  // The below methods are required to satisfy the Relay container contract.

  getFragmentNames() {
    return Object.keys(this.fragmentSpecs);
  }

  getFragment(fragmentName, variableMapping) {
    const { component, queryName } = this.fragmentSpecs[fragmentName];
    return component.getFragment(queryName, variableMapping);
  }

  hasFragment(fragmentName) {
    return this.fragmentSpecs[fragmentName] !== undefined;
  }

  hasVariable(variableName) {
    // It doesn't matter what the component variables are. The only variables
    // we're going to pass down are the ones defined from our route parameters.
    return Object.prototype.hasOwnProperty.call(
      this.queryConfig.params, variableName
    );
  }
}
<commit_msg>Go back to original logic, with legal characters<commit_after>import invariant from 'invariant';
import isEqual from 'lodash/isEqual';
import Relay from 'react-relay';

import getRouteQueries from './utils/getRouteQueries';
import mergeRouteParams from './utils/mergeRouteParams';

const DEFAULT_KEY = '_default';

export default class QueryAggregator {
  constructor(routerProps) {
    // We need to use a map to track route indices instead of throwing them on
    // the route itself with a Symbol to ensure that, when rendering on the
    // server, each request generates route indices independently.
    this.routeIndices = new WeakMap();
    this.lastRouteIndex = 0;

    this.queryConfig = null;
    this.fragmentSpecs = null;

    this.props = {};
    this.readyState = null;

    this.updateQueryConfig(routerProps);
  }

  updateQueryConfig(routerProps) {
    const { routes, components } = routerProps;

    const queryConfig = {
      name: null,
      queries: {},
      params: {},
    };
    const fragmentSpecs = {};

    routes.forEach((route, i) => {
      // We need to merge in the route params regardless of whether the route
      // actually has queries, in case its children depend on its path params.
      queryConfig.params = mergeRouteParams(
        queryConfig.params, route, routerProps
      );

      const routeQueries = getRouteQueries(route, routerProps);
      if (!routeQueries) {
        return;
      }

      const routeComponent = components[i];

      let componentMap;
      let queryMap;
      if (typeof routeComponent === 'object') {
        componentMap = routeComponent;
        queryMap = routeQueries;
      } else {
        componentMap = { [DEFAULT_KEY]: routeComponent };
        queryMap = { [DEFAULT_KEY]: routeQueries };
      }

      Object.keys(componentMap).forEach(key => {
        const component = componentMap[key];
        const queries = queryMap[key];

        if (!queries) {
          return;
        }

        // In principle not all container component routes have to specify
        // queries, because some of them might somehow receive fragments from
        // their parents, but it would definitely be wrong to specify queries
        // for a component that isn't a container.
        invariant(
          Relay.isContainer(component),
          'relay-router-relay: Route with queries specifies component `%s` ' +
          'that is not a Relay container.',
          component && (component.displayName || component.name)
        );

        Object.keys(queries).forEach(queryName => {
          const query = queries[queryName];
          const uniqueQueryName =
            this.getUniqueQueryName(route, key, queryName);

          // Relay depends on the argument count of the query function, so try
          // to preserve it as well as possible.
          let wrappedQuery;
          if (query.length === 0) {
            // Relay doesn't like using the exact same query in multiple
            // places, so wrap it to prevent that when sharing queries between
            // routes.
            wrappedQuery = () => query();
          } else {
            // When not using the shorthand, we can control the injected
            // params, so restrict them to just the ones for the current route
            // and its ancestors.
            const paramsForRoute = queryConfig.params;

            // We need the query function to have > 0 arguments to hit the code
            // path for non-shorthand queries.
            /* eslint-disable no-unused-vars */
            wrappedQuery = _ => query(component, paramsForRoute);
            /* eslint-enable */
          }

          queryConfig.queries[uniqueQueryName] = wrappedQuery;
          fragmentSpecs[uniqueQueryName] = { component, queryName };
        });
      });
    });

    queryConfig.name =
      ['_aggregated', ...Object.keys(queryConfig.queries)].join('_');

    // RootContainer uses referential equality to check for route change, so
    // replace the route object entirely.
    this.queryConfig = queryConfig;
    this.fragmentSpecs = fragmentSpecs;
  }

  getUniqueQueryName(route, key, queryName) {
    // There might be some edge case here where the query changes but the route
    // object does not, in which case we'll keep using the old unique name.
    // Anybody who does that deserves whatever they get, though.

    // Prefer an explicit route name if specified.
    if (route.name) {
      // The slightly different template here ensures that we can't have
      // collisions with the below template.
      return `_${route.name}_${key}_${queryName}`;
    }

    // Otherwise, use referential equality on the route name to generate a
    // unique index.
    let routeIndex = this.routeIndices.get(route);
    if (routeIndex === undefined) {
      routeIndex = ++this.lastRouteIndex;
      this.routeIndices.set(route, routeIndex);
    }

    return `__route_${routeIndex}_${key}_${queryName}`;
  }

  setRenderArgs({ props, ...readyState }) {
    if (props) {
      this.props = props;
    }

    this.readyState = readyState;
  }

  getRenderArgs(route, key, queries, params) {
    return {
      ...this.readyState,
      props: this.getProps(route, key, queries, params),
    };
  }

  getProps(route, key = DEFAULT_KEY, queries, params) {
    // Check that the subset of parameters used for this route match those used
    // for the fetched data.
    for (const paramName of Object.keys(params)) {
      if (!isEqual(this.props[paramName], params[paramName])) {
        return null;
      }
    }

    const props = {};
    for (const queryName of Object.keys(queries)) {
      const uniqueQueryName = this.getUniqueQueryName(route, key, queryName);

      const value = this.props[uniqueQueryName];
      if (!value) {
        return null;
      }

      props[queryName] = value;
    }

    // Only return the props for the route if the query config params match and
    // all requested props are available. Otherwise, by assumption, the ready
    // state will have the correct "not ready" state.
    return props;
  }

  // The below methods are required to satisfy the Relay container contract.

  getFragmentNames() {
    return Object.keys(this.fragmentSpecs);
  }

  getFragment(fragmentName, variableMapping) {
    const { component, queryName } = this.fragmentSpecs[fragmentName];
    return component.getFragment(queryName, variableMapping);
  }

  hasFragment(fragmentName) {
    return this.fragmentSpecs[fragmentName] !== undefined;
  }

  hasVariable(variableName) {
    // It doesn't matter what the component variables are. The only variables
    // we're going to pass down are the ones defined from our route parameters.
    return Object.prototype.hasOwnProperty.call(
      this.queryConfig.params, variableName
    );
  }
}
<|endoftext|>


<commit_before>/*

rest3d-server.js

The MIT License (MIT)

Copyright (c) 2013 Rémi Arnaud - Advanced Micro Devices, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

'use strict';

if (process.env['NODETIME_KEY']) {
  console.log("+=+=+= Starting http://nodetime.com");
  console.log("Key = '"+process.env['NODETIME_KEY']+"'");
  require('nodetime').profile({
    accountKey: process.env['NODETIME_KEY'],
    appName: 'rest3d.fl4re.com node server'
  });
}

var restify = require('restify');
//var connect = require('connect');
var http = require('http');
var utils = require('./src/utils');
var childProcess = require('child_process');
var os= require('os');
require('shelljs/global');

var fs = require('fs');
var path = require('path');
var cache = require('./src/diskcache');

// get content from zip files
var zip = require("zip");

var formidable = require('formidable');

var rmdirSync = require('./src/rmdir');
var copyFileSync = require('./src/cp');

var toJSON = require('./src/tojson');

var sendFile = require('./src/sendfile');
var Handler= require('./src/handler');

var platform = os.type().match(/^Win/) ? 'win' :
        (os.type().match(/^Dar/) ? 'mac' : 'unix');

console.log('host platform=',platform);
var staticPath = path.join(__dirname , '../static');

console.log('static folder=',staticPath);

var httpPort = process.env.OPENSHIFT_NODEJS_PORT ||
                   process.env.HTTP ||
                   8000;
var httpsPort = process.env.HTTPS || 443;
var ip_address = process.env.OPENSHIFT_NODEJS_IP || null;
var listenToPort = httpPort;

// see where collada2gltf is located
var openshift = process.env.OPENSHIFT_DATA_DIR;
var collada2gltf = 'collada2gltf';
if (openshift)
    collada2gltf = openshift + 'bin/collada2gltf-latest';
if (process.env.GLTF_BIN_PATH)
    collada2gltf = process.env.GLTF_BIN_PATH+'/collada2gltf';

// find out if we are running http or https
// if https, create two servers, the http server only does redirect to https

var params = {name: 'rest3d'};

if (fs.existsSync('./server.pem') && fs.existsSync('./server.key')){
  console.log('found https certficates');
  params.key = fs.readFileSync('./server.key');
  params.certificate = fs.readFileSync('./server.pem');
  listenToPort = httpsPort;
};

var server = module.exports.server = restify.createServer(params);

if (params.key) {
  var http_server = restify.createServer();
  http_server.get(/.*/,function(req,res,next) {
    // Assume https port is on the default port
    var redirect = "https://" + req.headers.host.replace (/(.*):.*/, '$1') +  req.url;
    res.writeHead(302, {'Location': redirect});

    //console.log('**** https redirect to '+redirect)
    res.end();
    next();
  });
  http_server.listen( httpPort, ip_address);
}

rmdirSync('tmp');
rmdirSync('cache');
rmdirSync('upload');

fs.mkdirSync('tmp');
fs.mkdirSync('upload');
fs.mkdirSync('cache');

fs.chmodSync('tmp', '777');
fs.chmodSync('upload', '777');
fs.chmodSync('cache', '777');

//fs.mkdirSync('upload/thumbnail');

server.use(restify.acceptParser(server.acceptable));
//server.use(restify.authorizationParser());
server.use(restify.dateParser());
server.use(restify.queryParser());
//server.use(restify.bodyParser()); -> use formidable instead
server.use(restify.gzipResponse());
restify.defaultResponseHeaders = false;

var session=require('./src/session')();
server.sessionManager = session;
server.use(session.sessionManager);

// include routes
require('./src/warehouse')(server);
require('./src/3dvia')(server);
require('./src/upload')(server);

// create diskcache (no mem caching, no gzip)
server.diskcache = new cache('cache',true,false,false);

function unknownMethodHandler(req, res) {
  console.log('unkownMethodHandler method='+req.method.toLowerCase());
  if (req.method.toLowerCase() === 'options') {
    var allowHeaders = ['Accept', 'Accept-Version', 'Content-Type', 'Api-Version'];

    if (res.methods.indexOf('OPTIONS') === -1) res.methods.push('OPTIONS');

    res.header('Access-Control-Allow-Credentials', true);
    res.header('Access-Control-Allow-Headers', allowHeaders.join(', '));
    res.header('Access-Control-Allow-Methods', res.methods.join(', '));
    res.header('Access-Control-Allow-Origin', req.headers.origin);

    return res.send(204);
  }
  else
    return res.send(new restify.MethodNotAllowedError());
}

server.on('MethodNotAllowed', unknownMethodHandler);
/*
server.use(restify.throttle({
  burst: 100,
  rate: 50,
  ip: true,
  overrides: {
    '192.168.1.1': {
      rate: 0,        // unlimited
      burst: 0
    }
  }
}));
*/
//server.use(restify.conditionalRequest());

// start database

var database =  process.env.DATABASE ||
                process.argv[2] ||
                   'existdb';

console.log("loading database module ["+database+"]");

// this initializes server.db
require('./src/'+database)(server);

server.db.init(function(b){
  if (b)
    console.log("rest3d will be using database ["+database+"]");
  else {
    console.log("rest3d will not be using database ["+database+"]");
    server.db=null;
  }

  // session mnager -> requires database
  session.config.db=server.db;

});

// rest3d API

server.get(/^\/rest3d\/info/,function(req, res, next) {
  var handler = new Handler(req,res,next);
  if (server.db) {
    handler.handleResult("database "+server.db.name+" connected");
  } else {
    handler.handleResult("database not connected")
  }
});

// convert

server.post(/^\/rest3d\/convert.*/,function(req,res,next){
   console.log('post -> convert');

   var form = new formidable.IncomingForm(),
         url = '',
         params = {};

   var handler = new Handler(req,res, next);

     form.on('field', function (name, data) {
      params[name] = data;
     }).on('error', function (e) {
      handler.handleError(e);
     }).on('end', function(){//
      console.log(params);
      console.log('now converting collada');
      if (!params.name && !params.path || !params.name.toLowerCase().endsWith('dae')) {
        h.handleError({error: 'invalid file '+params.name+' in convert'});
        return;
      }
      if(params.hasOwnProperty("path")){
        // new API, path specified in the params of the request in order to convert the right file.
        var output_dir = params.path.split('/');
        output_dir[output_dir.length-2] = output_dir[output_dir.length-2] + '_gltf';
        output_dir[output_dir.length-1] = output_dir[output_dir.length-1].replace('.dae','.json').replace('.DAE','.json');
        var output_path=output_dir[0];
        for(var i =1;i<output_dir.length;i++){
          output_path = output_path + "/" +output_dir[i];
          var list = fs.readdirSync("upload")
                list.forEach(function (name) {
                  console.log(name);
            });
          console.log(output_path,fs.existsSync(output_path+"/"));
          if(!fs.existsSync(output_path)&&i!==output_dir.length-1){
            console.log("create folder "+output_path)
            fs.mkdirSync(output_path);
          }
        }
        console.log(collada2gltf+" -p -f \"" + params.path+"\" -o \""+output_path+"\"");
        var cmd = collada2gltf+" -p -f \"" + params.path+"\" -o \""+output_path+"\"";
        var input_dir = params.path.replace(/[^\/]*$/,'');
        output_dir = output_path.replace(/[^\/]*$/,'');
      }
      else{
        // let's considered the model is stocked under rest3d/upload/ repository in case any path isn't specified
        // the conversion will create a folder for stocking the gltf file
        // CODE NOT TESTED, maybe get some conflicts for copying all textures to the gltf folder.
        console.log("PART NOT TESTED YET")
        var output_dir = params.name.split('\.')[0]+'_gltf';
        var output_file = params.name.replace('.dae','.json');
        var output_path = 'upload/'+output_dir+'/'+output_file;
        var cmd = collada2gltf+" -p -f \"upload/" + params.name+"\" -o \""+output_path+"\"";
         var input_dir = "upload/";
        output_dir = output_path.replace(/[^\/]*$/,'');
     }
      console.log('exec '+cmd);
      // todo -> manage progress !!!
    var outputC2J;
      var codeC2J;
      // todo -> manage progress !!!
    var ls = childProcess.exec(cmd, function (error, stdout, stderr) {
       if (error) {
         console.log(error.stack);
         //console.log('Error code: '+error.code);
         //console.log('Signal received: '+error.signal);

       handler.handleError({"code":error.code, "message": stderr});

       }
       console.log('Child Process STDOUT: '+stdout);
       console.log('Child Process STDERR: '+stderr);
     });
     ls.on('exit', function (code, output) {
      console.log('Child process exited with exit code '+code);
      if (code !== 0) {
        handler.handleError({errorCode:code, message:'Child process exited with exit code '});
        return;
      }
      codeC2J= code;
      outputC2J = output;

        console.log(input_dir)
      console.log('Exit code:', code);
        console.log('Program output:', output);

        console.log(input_dir)
      // // hack, copy all images in the output_dir, so the viewer will work
        var list = fs.readdirSync(input_dir);
        list.forEach(function (name) {
          var ext = name.match(/\.[^.]+$/);
          console.log(name,ext);
          if (ext[0]!=='.json'&&ext[0]!=='.dae'&&ext!=="null")
          {
            console.log("in");
            copyFileSync(input_dir+name, output_dir+name);
            console.log(input_dir+name+'  TO  '+output_dir+name);
          }
          else if(ext=="null"){
            console.log("Folder detected");
          }
          });
        // end hack
      var files = [];
      fs.readdir(output_dir, function (err, list) {
          list.forEach(function (name) {
              var stats = fs.statSync(output_dir+name);
              files.push({name: output_dir+name, size: stats.size});
          });
          var timeout = function() {
                rmdirSync(output_dir);
                console.log('timeout !! '+output_dir+' was deleted');
              }
          setTimeout(function() { timeout()},5 * 60 * 1000);
          handler.handleResult({files: files, code:codeC2J, output:outputC2J});
        });
       });
       });
    form.parse(req);
});

// static server
server.get(/^\/.*/, function (req, res, next) {

  // parse out parameters from url
  var filename = req.url.split('\?')[0];
  var p=path.resolve(staticPath + filename);

  console.log('http get path='+filename);

  sendFile(req,res,p);
  return next();
});

// clean exit
function sigterm_handler() {
    console.warn('Kaboom Baby!');
    process.exit(0);
}

process.on('SIGTERM', sigterm_handler);

// run server
server.listen( listenToPort, ip_address);
console.log ('rest3d server listening on port '+listenToPort);

<commit_msg>Touch<commit_after>/*

rest3d-server.js

The MIT License (MIT)

Copyright (c) 2013 Rémi Arnaud - Advanced Micro Devices, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

'use strict';

if (process.env['NODETIME_KEY']) {
  console.log("+=+=+= Starting http://nodetime.com");
  console.log("Key = '"+process.env['NODETIME_KEY']+"'");
  require('nodetime').profile({
    accountKey: process.env['NODETIME_KEY'],
    appName: 'rest3d.fl4re.com node server'
  });
}

var restify = require('restify');
//var connect = require('connect');
var http = require('http');
var utils = require('./src/utils');
var childProcess = require('child_process');
var os= require('os');
require('shelljs/global');

var fs = require('fs');
var path = require('path');
var cache = require('./src/diskcache');

// get content from zip files
var zip = require("zip");

var formidable = require('formidable');

var rmdirSync = require('./src/rmdir');
var copyFileSync = require('./src/cp');

var toJSON = require('./src/tojson');

var sendFile = require('./src/sendfile');
var Handler= require('./src/handler');

var platform = os.type().match(/^Win/) ? 'win' :
        (os.type().match(/^Dar/) ? 'mac' : 'unix');

console.log('host platform=',platform);
var staticPath = path.join(__dirname , '../static');

console.log('static folder=',staticPath);

var httpPort = process.env.OPENSHIFT_NODEJS_PORT ||
                   process.env.HTTP ||
                   8000;
var httpsPort = process.env.HTTPS || 443;
var ip_address = process.env.OPENSHIFT_NODEJS_IP || null;
var listenToPort = httpPort;

// see where collada2gltf is located
var openshift = process.env.OPENSHIFT_DATA_DIR;
var collada2gltf = 'collada2gltf';
if (openshift)
    collada2gltf = openshift + 'bin/collada2gltf-latest';
if (process.env.GLTF_BIN_PATH)
    collada2gltf = process.env.GLTF_BIN_PATH+'/collada2gltf';

// find out if we are running http or https
// if https, create two servers, the http server only does redirect to https

var params = {name: 'rest3d'};

if (fs.existsSync('./server.pem') && fs.existsSync('./server.key')){
  console.log('found https certficates');
  params.key = fs.readFileSync('./server.key');
  params.certificate = fs.readFileSync('./server.pem');
  listenToPort = httpsPort;
};

var server = module.exports.server = restify.createServer(params);

if (params.key) {
  var http_server = restify.createServer();
  http_server.get(/.*/,function(req,res,next) {
    // Assume https port is on the default port
    var redirect = "https://" + req.headers.host.replace (/(.*):.*/, '$1') +  req.url;
    res.writeHead(302, {'Location': redirect});

    //console.log('**** https redirect to '+redirect)
    res.end();
    next();
  });
  http_server.listen( httpPort, ip_address);
}

rmdirSync('tmp');
rmdirSync('cache');
rmdirSync('upload');

fs.mkdirSync('tmp');
fs.mkdirSync('upload');
fs.mkdirSync('cache');

fs.chmodSync('tmp', '777');
fs.chmodSync('upload', '777');
fs.chmodSync('cache', '777');

//fs.mkdirSync('upload/thumbnail');

server.use(restify.acceptParser(server.acceptable));
//server.use(restify.authorizationParser());
server.use(restify.dateParser());
server.use(restify.queryParser());
//server.use(restify.bodyParser()); -> use formidable instead
server.use(restify.gzipResponse());
restify.defaultResponseHeaders = false;

var session=require('./src/session')();
server.sessionManager = session;
server.use(session.sessionManager);

// include routes
require('./src/warehouse')(server);
require('./src/3dvia')(server);
require('./src/upload')(server);

// create diskcache (no mem caching, no gzip)
server.diskcache = new cache('cache',true,false,false);

function unknownMethodHandler(req, res) {
  console.log('unkownMethodHandler method='+req.method.toLowerCase());
  if (req.method.toLowerCase() === 'options') {
    var allowHeaders = ['Accept', 'Accept-Version', 'Content-Type', 'Api-Version'];

    if (res.methods.indexOf('OPTIONS') === -1) res.methods.push('OPTIONS');

    res.header('Access-Control-Allow-Credentials', true);
    res.header('Access-Control-Allow-Headers', allowHeaders.join(', '));
    res.header('Access-Control-Allow-Methods', res.methods.join(', '));
    res.header('Access-Control-Allow-Origin', req.headers.origin);

    return res.send(204);
  }
  else
    return res.send(new restify.MethodNotAllowedError());
}

server.on('MethodNotAllowed', unknownMethodHandler);
/*
server.use(restify.throttle({
  burst: 100,
  rate: 50,
  ip: true,
  overrides: {
    '192.168.1.1': {
      rate: 0,        // unlimited
      burst: 0
    }
  }
}));
*/
//server.use(restify.conditionalRequest());

// start database

var database =  process.env.DATABASE ||
                process.argv[2] ||
                   'existdb';

console.log("loading database module ["+database+"]");

// this initializes server.db
require('./src/'+database)(server);

server.db.init(function(b){
  if (b)
    console.log("rest3d will be using database ["+database+"]");
  else {
    console.log("rest3d will not be using database ["+database+"]");
    server.db=null;
  }

  // session mnager -> requires database
  session.config.db=server.db;

});

// rest3d API

server.get(/^\/rest3d\/info/,function(req, res, next) {
  var handler = new Handler(req,res,next);
  if (server.db) {
    handler.handleResult("database "+server.db.name+" connected");
  } else {
    handler.handleResult("database not connected")
  }
});

// convert

server.post(/^\/rest3d\/convert.*/,function(req,res,next){
   console.log('post -> convert');

   var form = new formidable.IncomingForm(),
         url = '',
         params = {};

   var handler = new Handler(req,res, next);

     form.on('field', function (name, data) {
      params[name] = data;
     }).on('error', function (e) {
      handler.handleError(e);
     }).on('end', function(){//
      console.log(params);
      console.log('now converting collada');
      if (!params.name && !params.path || !params.name.toLowerCase().endsWith('dae')) {
        h.handleError({error: 'invalid file '+params.name+' in convert'});
        return;
      }
      if(params.hasOwnProperty("path")){
        // new API, path specified in the params of the request in order to convert the right file.
        var output_dir = params.path.split('/');
        output_dir[output_dir.length-2] = output_dir[output_dir.length-2] + '_gltf';
        output_dir[output_dir.length-1] = output_dir[output_dir.length-1].replace('.dae','.json').replace('.DAE','.json');
        var output_path=output_dir[0];
        for(var i =1;i<output_dir.length;i++){
          output_path = output_path + "/" +output_dir[i];
          var list = fs.readdirSync("upload")
                list.forEach(function (name) {
                  console.log(name);
            });
          console.log(output_path,fs.existsSync(output_path+"/"));
          if(!fs.existsSync(output_path)&&i!==output_dir.length-1){
            console.log("create folder "+output_path)
            fs.mkdirSync(output_path);
          }
        }
        console.log(collada2gltf+" -p -f \"" + params.path+"\" -o \""+output_path+"\"");
        var cmd = collada2gltf+" -p -f \"" + params.path+"\" -o \""+output_path+"\"";
        var input_dir = params.path.replace(/[^\/]*$/,'');
        output_dir = output_path.replace(/[^\/]*$/,'');
      }
      else{
        // let's considered the model is stocked under rest3d/upload/ repository in case any path isn't specified
        // the conversion will create a folder for stocking the gltf file
        // CODE NOT TESTED, maybe get some conflicts for copying all textures to the gltf folder.
        console.log("PART NOT TESTED YET")
        var output_dir = params.name.split('\.')[0]+'_gltf';
        var output_file = params.name.replace('.dae','.json');
        var output_path = 'upload/'+output_dir+'/'+output_file;
        var cmd = collada2gltf+" -p -f \"upload/" + params.name+"\" -o \""+output_path+"\"";
         var input_dir = "upload/";
        output_dir = output_path.replace(/[^\/]*$/,'');
     }
      console.log('exec '+cmd);
      // todo -> manage progress !!!
    var outputC2J;
      var codeC2J;
      // todo -> manage progress !!!
    var ls = childProcess.exec(cmd, function (error, stdout, stderr) {
       if (error) {
         console.log(error.stack);
         //console.log('Error code: '+error.code);
         //console.log('Signal received: '+error.signal);

       handler.handleError({"code":error.code, "message": stderr});

       }
       console.log('Child Process STDOUT: '+stdout);
       console.log('Child Process STDERR: '+stderr);
     });
     ls.on('exit', function (code, output) {
      console.log('Child process exited with exit code '+code);
      if (code !== 0) {
        handler.handleError({errorCode:code, message:'Child process exited with exit code '});
        return;
      }
      codeC2J= code;
      outputC2J = output;

        console.log(input_dir)
      console.log('Exit code:', code);
        console.log('Program output:', output);

        console.log(input_dir)
      // // hack, copy all images in the output_dir, so the viewer will work
        var list = fs.readdirSync(input_dir);
        list.forEach(function (name) {
          var ext = name.match(/\.[^.]+$/);
          console.log(name,ext);
          if(ext!=="null"){
            if (ext[0]!=='.json'&&ext[0]!=='.dae'&&)
            {
              copyFileSync(input_dir+name, output_dir+name);
              console.log(input_dir+name+'  TO  '+output_dir+name);
            }
          }
          else{
            console.log("Folder detected");
          }
          });
        // end hack
      // var files = [];
      // fs.readdir(output_dir, function (err, list) {
      //     list.forEach(function (name) {
      //         var stats = fs.statSync(output_dir+name);
      //         files.push({name: output_dir+name, size: stats.size});
      //     });
      //     var timeout = function() {
      //           rmdirSync(output_dir);
      //           console.log('timeout !! '+output_dir+' was deleted');
      //         }
      //     setTimeout(function() { timeout()},5 * 60 * 1000);
      //     handler.handleResult({files: files, code:codeC2J, output:outputC2J});
      //   });
       });
       });
    form.parse(req);
});

// static server
server.get(/^\/.*/, function (req, res, next) {

  // parse out parameters from url
  var filename = req.url.split('\?')[0];
  var p=path.resolve(staticPath + filename);

  console.log('http get path='+filename);

  sendFile(req,res,p);
  return next();
});

// clean exit
function sigterm_handler() {
    console.warn('Kaboom Baby!');
    process.exit(0);
}

process.on('SIGTERM', sigterm_handler);

// run server
server.listen( listenToPort, ip_address);
console.log ('rest3d server listening on port '+listenToPort);

<|endoftext|>


<commit_before>const Router = require('express').Router();
const DB = require('../database/db');

Router.post('/', (req, res) => {
    const { documentID, userID } = req.body;

    const rating = Number.parseInt(req.body.rating);

    if(!documentID || !userID || !rating || rating > 5) {
        res.statusCode = 400;
        res.json({
            error: true,
            success: false,
            errmsg: 'Missing/Invalid fields!'
        });
        res.end();
        return;
    }
    DB.getConnection((err, con) => {
        if(err) throw err;

        let sql = 'INSERT INTO rating(ratingID, userID, documentID, rating) '+
                  'VALUES(null, ?, ?, ?)';
        con.query(sql, [Number.parseInt(userID), Number.parseInt(documentID), rating], err => {
            if(err) throw err;

            res.json({
                success: true,
                error: false
            });
            res.end();
        });
    });

});

module.exports = Router;<commit_msg>release database connection<commit_after>const Router = require('express').Router();
const DB = require('../database/db');

Router.post('/', (req, res) => {
    const { documentID, userID } = req.body;

    const rating = Number.parseInt(req.body.rating);

    if(!documentID || !userID || !rating || rating > 5) {
        res.statusCode = 400;
        res.json({
            error: true,
            success: false,
            errmsg: 'Missing/Invalid fields!'
        });
        res.end();
        return;
    }
    DB.getConnection((err, con) => {
        if(err) throw err;

        let sql = 'INSERT INTO rating(ratingID, userID, documentID, rating) '+
                  'VALUES(null, ?, ?, ?)';
        con.query(sql, [Number.parseInt(userID), Number.parseInt(documentID), rating], err => {
            if(err) throw err;

            res.json({
                success: true,
                error: false
            });
            res.end();
            con.release();
        });
    });

});

module.exports = Router;<|endoftext|>


<commit_before>// Unobtrusive scripting adapter for React
(function(document, window, React) {
  var CLASS_NAME_ATTR = 'data-react-class';
  var PROPS_ATTR = 'data-react-props';

  // jQuery is optional. Use it to support legacy browsers.
  var $ = (typeof jQuery !== 'undefined') && jQuery;

  var findReactDOMNodes = function() {
    var SELECTOR = '[' + CLASS_NAME_ATTR + ']';
    if ($) {
      return $(SELECTOR);
    } else {
      return document.querySelectorAll(SELECTOR);
    }
  };

  var mountReactComponents = function() {
    var nodes = findReactDOMNodes();
    for (var i = 0; i < nodes.length; ++i) {
      var node = nodes[i];
      var className = node.getAttribute(CLASS_NAME_ATTR);
      // Assume className is simple and can be found at top-level (window).
      // Fallback to eval to handle cases like 'My.React.ComponentName'.
      var constructor = window[className] || eval.call(window, className);
      var propsJson = node.getAttribute(PROPS_ATTR);
      var props = propsJson && JSON.parse(propsJson);
      React.renderComponent(constructor(props), node);
    }
  };

  var unmountReactComponents = function() {
    var nodes = findReactDOMNodes();
    for (var i = 0; i < nodes.length; ++i) {
      React.unmountComponentAtNode(nodes[i]);
    }
  };

  var handleTurbolinksEvents = function() {
    var handleEvent;
    if ($) {
      handleEvent = function(eventName, callback) {
        $(document).on(eventName, callback);
      }
    } else {
      handleEvent = function(eventName, callback) {
        document.addEventListener(eventName, callback);
      }
    }
    handleEvent('page:change', mountReactComponents);
    handleEvent('page:receive', unmountReactComponents);
  };

  var handleNativeEvents = function() {
    if ($) {
      $(mountReactComponents);
      $(window).unload(unmountReactComponents);
    } else {
      document.addEventListener('DOMContentLoaded', mountReactComponents);
      window.addEventListener('unload', unmountReactComponents);
    }
  };

  typeof Turbolinks !== 'undefined' ? handleTurbolinksEvents() : handleNativeEvents();
})(document, window, React);
<commit_msg>Only use turbolinks events if supported<commit_after>// Unobtrusive scripting adapter for React
(function(document, window, React) {
  var CLASS_NAME_ATTR = 'data-react-class';
  var PROPS_ATTR = 'data-react-props';

  // jQuery is optional. Use it to support legacy browsers.
  var $ = (typeof jQuery !== 'undefined') && jQuery;

  var findReactDOMNodes = function() {
    var SELECTOR = '[' + CLASS_NAME_ATTR + ']';
    if ($) {
      return $(SELECTOR);
    } else {
      return document.querySelectorAll(SELECTOR);
    }
  };

  var mountReactComponents = function() {
    var nodes = findReactDOMNodes();
    for (var i = 0; i < nodes.length; ++i) {
      var node = nodes[i];
      var className = node.getAttribute(CLASS_NAME_ATTR);
      // Assume className is simple and can be found at top-level (window).
      // Fallback to eval to handle cases like 'My.React.ComponentName'.
      var constructor = window[className] || eval.call(window, className);
      var propsJson = node.getAttribute(PROPS_ATTR);
      var props = propsJson && JSON.parse(propsJson);
      React.renderComponent(constructor(props), node);
    }
  };

  var unmountReactComponents = function() {
    var nodes = findReactDOMNodes();
    for (var i = 0; i < nodes.length; ++i) {
      React.unmountComponentAtNode(nodes[i]);
    }
  };

  var handleTurbolinksEvents = function() {
    var handleEvent;
    if ($) {
      handleEvent = function(eventName, callback) {
        $(document).on(eventName, callback);
      }
    } else {
      handleEvent = function(eventName, callback) {
        document.addEventListener(eventName, callback);
      }
    }
    handleEvent('page:change', mountReactComponents);
    handleEvent('page:receive', unmountReactComponents);
  };

  var handleNativeEvents = function() {
    if ($) {
      $(mountReactComponents);
      $(window).unload(unmountReactComponents);
    } else {
      document.addEventListener('DOMContentLoaded', mountReactComponents);
      window.addEventListener('unload', unmountReactComponents);
    }
  };

  if (typeof Turbolinks !== 'undefined' && Turbolinks.supported) {
    handleTurbolinksEvents();
  } else {
    handleNativeEvents();
  }
})(document, window, React);
<|endoftext|>