<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var express = require("express");
var conf = require("./config").conf;
var pjson = require("../package.json");
var raven = require("raven");
var phone = require("phone");
var cors = require("cors");
var errors = require("connect-validation");
var logging = require("express-logging");
var headers = require("./headers");
var digitsCode = require("./utils").digitsCode;
var smsGateway = require("./sms-gateway");
var hmac = require("./hmac");
var validateMSISDN = require("./middleware").validateMSISDN;
var Token = require("./token").Token;
var validateJWCryptoKey = require("./utils").validateJWCryptoKey;
var Hawk = require('hawk');

var jwcrypto = require('jwcrypto');

// Make sure to load supported algorithms.
require('jwcrypto/lib/algs/rs');
require('jwcrypto/lib/algs/ds');

var _publicKey = jwcrypto.loadPublicKeyFromObject(conf.get('BIDPublicKey'));
var _privKey = jwcrypto.loadSecretKeyFromObject(conf.get('BIDSecretKey'));

var ravenClient = new raven.Client(conf.get("sentryDSN"));

var getStorage = require("./storage");
var storage = getStorage(conf.get("storage"));

var DIGIT_CODE_SIZE = 6;

function logError(err) {
  console.log(err);
  ravenClient.captureError(err);
}

var app = express();

if (conf.get("env") === "development") {
  app.use(logging(conf.get("consoleDateFormat")));
}
app.use(headers);
app.disable("x-powered-by");
app.use(express.json());
app.use(express.urlencoded());
app.use(errors);
app.use(app.router);
// Exception logging should come at the end of the list of middlewares.
app.use(raven.middleware.express(conf.get("sentryDSN")));

var corsEnabled = cors({
  origin: function(origin, callback) {
    var acceptedOrigin = conf.get("allowedOrigins").indexOf(origin) !== -1;
    callback(null, acceptedOrigin);
  },
  // Configures the Access-Control-Allow-Credentials CORS header, required
  // until we stop sending cookies.
  credentials: true
});

function requireParams() {
  var params = Array.prototype.slice.call(arguments);
  return function(req, res, next) {
    var missingParams;

    if (!req.accepts("json")) {
      res.json(406, ["application/json"]);
      return;
    }

    missingParams = params.filter(function(param) {
      return req.body[param] === undefined;
    });

    if (missingParams.length > 0) {
      missingParams.forEach(function(item) {
        res.addError("body", item, "missing: " + item);
      });
      res.sendError();
    }
    next();
  };
}

/**
 * The Hawk middleware.
 *
 * Checks that the requests are authenticated with hawk, and sign the
 * responses.
 */
function hawkMiddleware(req, res, next) {
  Hawk.server.authenticate(req, function(id, callback) {
    var client = getStorage(conf.get("storage"));
    client.getSession(id, callback);
  }, {},
    function(err, credentials, artifacts) {
      req.hawk = artifacts;

      if (err) {
        if (!err.isMissing) {
          logError(err, artifacts);
        }

        // In case no supported authentication was specified, challenge the
        // client.

        res.setHeader("WWW-Authenticate",
                      err.output.headers["WWW-Authenticate"]);
        res.json(401, err.output.payload);
        return;
      }

      if (credentials === null) {
        res.json(403, "Forbidden");
        return;
      }

      /* Make sure we don't decorate the writeHead more than one time. */
      if (res._hawkEnabled) {
        next();
        return;
      }

      var writeHead = res.writeHead;
      res._hawkEnabled = true;
      res.writeHead = function hawkWriteHead() {
        var header = Hawk.server.header(
          credentials, artifacts, {
            payload: res.body,
            contentType: res.get('Content-Type')
          });
        res.setHeader("Server-Authorization", header);
        writeHead.apply(res, arguments);
      };
      next();
    });
}

/**
 * Enable CORS for all requests.
 **/
app.all("*", corsEnabled);

/**
 * Checks that the service and its dependencies are healthy.
 **/
app.get("/__heartbeat__", function(req, res) {
  storage.ping(function(storageStatus) {
    var status;
    if (storageStatus === true) {
      status = 200;
    } else {
      status = 503;
    }

    res.json(status, {
      storage: storageStatus
    });
  });
});

/**
 * Displays some version information at the root of the service.
 **/
app.get("/", function(req, res) {
  var serverInfo = {
    name: pjson.name,
    description: pjson.description,
    version: pjson.version,
    homepage: pjson.homepage,
    endpoint: req.protocol + "://" + req.get("host")
  };

  if (!conf.get("displayVersion")) {
    delete serverInfo.version;
  }
  res.json(200, serverInfo);
});

/**
 * Return the best verification method wrt msisdn, mcc, mnc, roaming
 **/
app.post("/discover", function(req, res) {
  var verificationMethods = [], verificationDetails = {}, url;

  if (req.body.hasOwnProperty("msisdn")) {
    var msisdn = phone(req.body.msisdn);
    if (msisdn === null) {
      res.sendError("body", "msisdn", "Invalid MSISDN number.");
      return;
    }
    // SMS/MT methods configuration
    url = req.protocol + "://" + req.get("host") +
          conf.get("apiPrefix") + "/sms/mt/verify";

    verificationMethods.push("sms/mt");
    verificationDetails["sms/mt"] = {
      mtSender: conf.get("mtSender"),
      url: url
    };
  }

  // SMS/MOMT methods configuration
  verificationMethods.push("sms/momt");
  verificationDetails["sms/momt"] = {
    mtSender: conf.get("mtSender"),
    moVerifier: conf.get("moVerifier")
  };

  res.json(200, {
    verificationMethods: verificationMethods,
    verificationDetails: verificationDetails
  });
});

/**
 * Ask for a new number registration.
 **/
app.post("/register", function(req, res) {

  var token = new Token();
  token.getCredentials(function(tokenId, authKey, sessionToken) {
    storage.setSession(tokenId, authKey, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }

      res.json(200, {
        msisdnSessionToken: sessionToken
      });
    });
  });
});

/**
 * Unregister the session.
 **/
app.post("/unregister", hawkMiddleware, requireParams("msisdn"),
  validateMSISDN, function(req, res) {
    storage.cleanSession(req.hawk.id, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }
      res.json(200, {});
    });
  });

/**
 * Ask for a new number registration.
 **/
app.post("/sms/mt/verify", hawkMiddleware, requireParams("msisdn"),
  validateMSISDN, function(req, res) {
    var code = digitsCode(DIGIT_CODE_SIZE);

    storage.setCode(req.msisdnId, code, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }
      /* Send SMS */
      // XXX export string in l10n external file.
      smsGateway.sendSMS(req.msisdn,
        "To validate your number please enter the following code: " + code +
        " ",
        function(err, data) {
          res.json(200, data);
        });
    });
  });

/**
 * Ask for a new verification code
 **/
app.post("/sms/mt/resend_code", hawkMiddleware, requireParams("msisdn"),
  validateMSISDN, function(req, res) {
    var code = digitsCode(DIGIT_CODE_SIZE);

    storage.setCode(req.msisdnId, code, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }
      /* Send SMS */
      smsGateway.sendSMS(req.msisdn,
        "To validate your number please enter the following code: " + code,
        function(err) {
          if (err) {
            logError(err);
            res.json(503, "Service Unavailable");
            return;
          }
          res.json(200, {});
        });
    });
  });

/**
 * Ask for a MO verification
 **/
app.post("/sms/momt/verify", hawkMiddleware, function(req, res) {
  var code = digitsCode(DIGIT_CODE_SIZE);

  var smsBody = hmac(req.hawk.id, conf.get("msisdnIdSecret"));

  storage.setSmsCode(smsBody, code, function(err) {
    if (err) {
      logError(err);
      res.json(503, "Service Unavailable");
        return;
    }
    res.json(200, {
      mtSender: conf.get("mtSender"),
      moVerifier: conf.get("moVerifier"),
      smsBody: smsBody
    });
  });
});

/**
 * Handle Mobile Originated SMS reception
 **/
app.get("/sms/momt/nexmo_callback", function(req, res) {
  var msisdn = phone(req.query.msisdn);
  var smsBody = req.query.text;

  storage.popSmsCode(smsBody, function(err, code) {
    if (err) {
      logError(err);
      res.json(503, "Service Unavailable");
      return;
    }

    if (code !== null && msisdn !== null) {
      var msisdnId = hmac(msisdn, conf.get("msisdnIdSecret"));

      storage.setCode(msisdnId, code, function(err) {
        if (err) {
          logError(err);
          res.json(503, "Service Unavailable");
          return;
        }

        /* Send SMS */
        smsGateway.sendSMS(msisdn, "Your number is: " + msisdn +
          ". To validate it please enter the following code: " + code,
          function(err) {
            if (err) {
              logError(err);
              res.json(503, "Service Unavailable");
              return;
            }
            res.json(200, {});
          });
        return;
      });
    }
    /* The callback is always waiting for a 200 or it will keep trying.
     * In that case the smsBody has expired so we don't want to replay it.
     */
    res.json(200, {});
  });
});

/**
 * Verify code
 **/
app.post("/sms/verify_code", hawkMiddleware, requireParams(
  "msisdn", "code", "duration", "publicKey"), validateMSISDN,
  function(req, res) {
    var code = req.body.code;
    var publicKey = req.body.publicKey;
    var duration = req.body.duration;

    // Validate code.
    if (code.length !== DIGIT_CODE_SIZE) {
      res.addError("body", "code",
                   "Code should be " + DIGIT_CODE_SIZE + " long.");
    }

    // Validate publicKey.
    try {
      validateJWCryptoKey(publicKey);
    } catch (err) {
      res.addError("body", "publicKey", err);
    }

    // Validate duration.
    if (typeof duration !== "number" || duration < 1) {
      res.addError("body", "duration",
                   "Duration should be a number of seconds.");
    }

    // Return errors found during validation.
    if (res.hasErrors()) {
      res.sendError();
      return;
    }

    storage.verifyCode(req.msisdnId, code, function(err, result) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }

      if (result === null) {
        res.json(410, "Code has expired.");
        return;
      }

      if (!result) {
        res.json(403, "Code error.");
        return;
      }

      // Generate a certificate
      var now = Date.now();

      jwcrypto.cert.sign({
        publicKey: jwcrypto.loadPublicKeyFromObject(publicKey),
        principal: req.msisdnId + "@" + req.get("host")
      }, {
        issuer: req.get("host"),
        // Set issuedAt to 10 seconds ago. Pads for verifier clock skew
        issuedAt: new Date(now - (10 * 1000)),
        expiresAt: new Date(now + duration)
      }, {
        "fxa-lastAuthAt": now,
        "fxa-verifiedMSISDN": req.msisdn
      }, _privKey, function(err, cert) {
        if (err) {
          logError(err);
          res.json(503, "Service Unavailable");
          return;
        }

        if (result === null) {
          res.json(410, "Code has expired.");
          return;
        }

        if (!result) {
          res.json(403, "Code error.");
          return;
        }

        // XXX Need to generate a certificate.
        var now = Date.now();

        jwcrypto.cert.sign({
          publicKey: jwcrypto.loadPublicKeyFromObject(publicKey),
          principal: req.msisdnId + "@" + req.get("host")
        }, {
          issuer: req.get("host"),
          // Set issuedAt to 10 seconds ago. Pads for verifier clock skew.
          issuedAt: new Date(now - (10 * 1000)),
          expiresAt: new Date(now + duration)
        }, {
          "fxa-lastAuthAt": now,
          "fxa-verifiedMSISDN": req.msisdn
        }, _privKey,
        function(err, cert) {
          if (err) {
            logError(err);
            res.json(503, "Service Unavailable");
            return;
          }
          res.json(200, {cert: cert, publicKey: _publicKey.serialize()});
        });
      });
    });
  });

/**
 * Ask for a new verification code.
 **/
app.post("/sms/mt/resend_code", hawkMiddleware, requireParams("msisdn"),
  validateMSISDN, function(req, res) {
    var code = digitsCode(DIGIT_CODE_SIZE);

    storage.setCode(req.msisdnId, code, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }
      /* Send SMS */
      smsGateway.sendSMS(req.msisdn,
        "To validate your number please enter the following code: " + code,
        function(err) {
          res.json(200, {});
        });
    });
  });

app.listen(conf.get("port"), conf.get("host"), function(){
  console.log("Server listening on http://" +
              conf.get("host") + ":" + conf.get("port"));
});

module.exports = {
  app: app,
  conf: conf,
  storage: storage,
  requireParams: requireParams
};
<commit_msg>Alexis review.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var express = require("express");
var conf = require("./config").conf;
var pjson = require("../package.json");
var raven = require("raven");
var phone = require("phone");
var cors = require("cors");
var errors = require("connect-validation");
var logging = require("express-logging");
var headers = require("./headers");
var digitsCode = require("./utils").digitsCode;
var smsGateway = require("./sms-gateway");
var hmac = require("./hmac");
var validateMSISDN = require("./middleware").validateMSISDN;
var Token = require("./token").Token;
var validateJWCryptoKey = require("./utils").validateJWCryptoKey;
var Hawk = require('hawk');

var jwcrypto = require('jwcrypto');

// Make sure to load supported algorithms.
require('jwcrypto/lib/algs/rs');
require('jwcrypto/lib/algs/ds');

var _publicKey = jwcrypto.loadPublicKeyFromObject(conf.get('BIDPublicKey'));
var _privKey = jwcrypto.loadSecretKeyFromObject(conf.get('BIDSecretKey'));

var ravenClient = new raven.Client(conf.get("sentryDSN"));

var getStorage = require("./storage");
var storage = getStorage(conf.get("storage"));

var DIGIT_CODE_SIZE = 6;

function logError(err) {
  console.log(err);
  ravenClient.captureError(err);
}

var app = express();

if (conf.get("env") === "development") {
  app.use(logging(conf.get("consoleDateFormat")));
}
app.use(headers);
app.disable("x-powered-by");
app.use(express.json());
app.use(express.urlencoded());
app.use(errors);
app.use(app.router);
// Exception logging should come at the end of the list of middlewares.
app.use(raven.middleware.express(conf.get("sentryDSN")));

var corsEnabled = cors({
  origin: function(origin, callback) {
    var acceptedOrigin = conf.get("allowedOrigins").indexOf(origin) !== -1;
    callback(null, acceptedOrigin);
  },
  // Configures the Access-Control-Allow-Credentials CORS header, required
  // until we stop sending cookies.
  credentials: true
});

function requireParams() {
  var params = Array.prototype.slice.call(arguments);
  return function(req, res, next) {
    var missingParams;

    if (!req.accepts("json")) {
      res.json(406, ["application/json"]);
      return;
    }

    missingParams = params.filter(function(param) {
      return req.body[param] === undefined;
    });

    if (missingParams.length > 0) {
      missingParams.forEach(function(item) {
        res.addError("body", item, "missing: " + item);
      });
      res.sendError();
    }
    next();
  };
}

/**
 * The Hawk middleware.
 *
 * Checks that the requests are authenticated with hawk, and sign the
 * responses.
 */
function hawkMiddleware(req, res, next) {
  Hawk.server.authenticate(req, function(id, callback) {
    var client = getStorage(conf.get("storage"));
    client.getSession(id, callback);
  }, {},
    function(err, credentials, artifacts) {
      req.hawk = artifacts;

      if (err) {
        if (!err.isMissing) {
          logError(err, artifacts);
        }

        // In case no supported authentication was specified, challenge the
        // client.

        res.setHeader("WWW-Authenticate",
                      err.output.headers["WWW-Authenticate"]);
        res.json(401, err.output.payload);
        return;
      }

      if (credentials === null) {
        res.json(403, "Forbidden");
        return;
      }

      /* Make sure we don't decorate the writeHead more than one time. */
      if (res._hawkEnabled) {
        next();
        return;
      }

      var writeHead = res.writeHead;
      res._hawkEnabled = true;
      res.writeHead = function hawkWriteHead() {
        var header = Hawk.server.header(
          credentials, artifacts, {
            payload: res.body,
            contentType: res.get('Content-Type')
          });
        res.setHeader("Server-Authorization", header);
        writeHead.apply(res, arguments);
      };
      next();
    });
}

/**
 * Enable CORS for all requests.
 **/
app.all("*", corsEnabled);

/**
 * Checks that the service and its dependencies are healthy.
 **/
app.get("/__heartbeat__", function(req, res) {
  storage.ping(function(storageStatus) {
    var status;
    if (storageStatus === true) {
      status = 200;
    } else {
      status = 503;
    }

    res.json(status, {
      storage: storageStatus
    });
  });
});

/**
 * Displays some version information at the root of the service.
 **/
app.get("/", function(req, res) {
  var serverInfo = {
    name: pjson.name,
    description: pjson.description,
    version: pjson.version,
    homepage: pjson.homepage,
    endpoint: req.protocol + "://" + req.get("host")
  };

  if (!conf.get("displayVersion")) {
    delete serverInfo.version;
  }
  res.json(200, serverInfo);
});

/**
 * Return the best verification method wrt msisdn, mcc, mnc, roaming
 **/
app.post("/discover", function(req, res) {
  var verificationMethods = [],
      verificationDetails = {},
      url;

  if (req.body.hasOwnProperty("msisdn")) {
    var msisdn = phone(req.body.msisdn);
    if (msisdn === null) {
      res.sendError("body", "msisdn", "Invalid MSISDN number.");
      return;
    }
    // SMS/MT methods configuration
    url = req.protocol + "://" + req.get("host") +
          conf.get("apiPrefix") + "/sms/mt/verify";

    verificationMethods.push("sms/mt");
    verificationDetails["sms/mt"] = {
      mtSender: conf.get("mtSender"),
      url: url
    };
  }

  // SMS/MOMT methods configuration
  verificationMethods.push("sms/momt");
  verificationDetails["sms/momt"] = {
    mtSender: conf.get("mtSender"),
    moVerifier: conf.get("moVerifier")
  };

  res.json(200, {
    verificationMethods: verificationMethods,
    verificationDetails: verificationDetails
  });
});

/**
 * Ask for a new number registration.
 **/
app.post("/register", function(req, res) {

  var token = new Token();
  token.getCredentials(function(tokenId, authKey, sessionToken) {
    storage.setSession(tokenId, authKey, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }

      res.json(200, {
        msisdnSessionToken: sessionToken
      });
    });
  });
});

/**
 * Unregister the session.
 **/
app.post("/unregister", hawkMiddleware, requireParams("msisdn"),
  validateMSISDN, function(req, res) {
    storage.cleanSession(req.hawk.id, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }
      res.json(200, {});
    });
  });

/**
 * Ask for a new number registration.
 **/
app.post("/sms/mt/verify", hawkMiddleware, requireParams("msisdn"),
  validateMSISDN, function(req, res) {
    var code = digitsCode(DIGIT_CODE_SIZE);

    storage.setCode(req.msisdnId, code, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }
      /* Send SMS */
      // XXX export string in l10n external file.
      smsGateway.sendSMS(req.msisdn,
        "To validate your number please enter the following code: " + code +
        " ",
        function(err, data) {
          res.json(200, data);
        });
    });
  });

/**
 * Ask for a new verification code
 **/
app.post("/sms/mt/resend_code", hawkMiddleware, requireParams("msisdn"),
  validateMSISDN, function(req, res) {
    var code = digitsCode(DIGIT_CODE_SIZE);

    storage.setCode(req.msisdnId, code, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }
      /* Send SMS */
      smsGateway.sendSMS(req.msisdn,
        "To validate your number please enter the following code: " + code,
        function(err) {
          if (err) {
            logError(err);
            res.json(503, "Service Unavailable");
            return;
          }
          res.json(200, {});
        });
    });
  });

/**
 * Ask for a MO verification
 **/
app.post("/sms/momt/verify", hawkMiddleware, function(req, res) {
  var code = digitsCode(DIGIT_CODE_SIZE);

  var smsBody = hmac(req.hawk.id, conf.get("msisdnIdSecret"));

  storage.setSmsCode(smsBody, code, function(err) {
    if (err) {
      logError(err);
      res.json(503, "Service Unavailable");
        return;
    }
    res.json(200, {
      mtSender: conf.get("mtSender"),
      moVerifier: conf.get("moVerifier"),
      smsBody: smsBody
    });
  });
});

/**
 * Handle Mobile Originated SMS reception
 **/
app.get("/sms/momt/nexmo_callback", function(req, res) {
  var msisdn = phone(req.query.msisdn);
  var smsBody = req.query.text;

  storage.popSmsCode(smsBody, function(err, code) {
    if (err) {
      logError(err);
      res.json(503, "Service Unavailable");
      return;
    }

    if (code !== null && msisdn !== null) {
      var msisdnId = hmac(msisdn, conf.get("msisdnIdSecret"));

      storage.setCode(msisdnId, code, function(err) {
        if (err) {
          logError(err);
          res.json(503, "Service Unavailable");
          return;
        }

        /* Send SMS */
        smsGateway.sendSMS(msisdn, "Your number is: " + msisdn +
          ". To validate it please enter the following code: " + code,
          function(err) {
            if (err) {
              logError(err);
              res.json(503, "Service Unavailable");
              return;
            }
            res.json(200, {});
          });
        return;
      });
    }
    /* The callback is always waiting for a 200 or it will keep trying.
     * In that case the smsBody has expired so we don't want to replay it.
     */
    res.json(200, {});
  });
});

/**
 * Verify code
 **/
app.post("/sms/verify_code", hawkMiddleware, requireParams(
  "msisdn", "code", "duration", "publicKey"), validateMSISDN,
  function(req, res) {
    var code = req.body.code;
    var publicKey = req.body.publicKey;
    var duration = req.body.duration;

    // Validate code.
    if (code.length !== DIGIT_CODE_SIZE) {
      res.addError("body", "code",
                   "Code should be " + DIGIT_CODE_SIZE + " long.");
    }

    // Validate publicKey.
    try {
      validateJWCryptoKey(publicKey);
    } catch (err) {
      res.addError("body", "publicKey", err);
    }

    // Validate duration.
    if (typeof duration !== "number" || duration < 1) {
      res.addError("body", "duration",
                   "Duration should be a number of seconds.");
    }

    // Return errors found during validation.
    if (res.hasErrors()) {
      res.sendError();
      return;
    }

    storage.verifyCode(req.msisdnId, code, function(err, result) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }

      if (result === null) {
        res.json(410, "Code has expired.");
        return;
      }

      if (!result) {
        res.json(403, "Code error.");
        return;
      }

      // Generate a certificate
      var now = Date.now();

      jwcrypto.cert.sign({
        publicKey: jwcrypto.loadPublicKeyFromObject(publicKey),
        principal: req.msisdnId + "@" + req.get("host")
      }, {
        issuer: req.get("host"),
        // Set issuedAt to 10 seconds ago. Pads for verifier clock skew
        issuedAt: new Date(now - (10 * 1000)),
        expiresAt: new Date(now + duration)
      }, {
        "fxa-lastAuthAt": now,
        "fxa-verifiedMSISDN": req.msisdn
      }, _privKey, function(err, cert) {
        if (err) {
          logError(err);
          res.json(503, "Service Unavailable");
          return;
        }

        if (result === null) {
          res.json(410, "Code has expired.");
          return;
        }

        if (!result) {
          res.json(403, "Code error.");
          return;
        }

        // XXX Need to generate a certificate.
        var now = Date.now();

        jwcrypto.cert.sign({
          publicKey: jwcrypto.loadPublicKeyFromObject(publicKey),
          principal: req.msisdnId + "@" + req.get("host")
        }, {
          issuer: req.get("host"),
          // Set issuedAt to 10 seconds ago. Pads for verifier clock skew.
          issuedAt: new Date(now - (10 * 1000)),
          expiresAt: new Date(now + duration)
        }, {
          "fxa-lastAuthAt": now,
          "fxa-verifiedMSISDN": req.msisdn
        }, _privKey,
        function(err, cert) {
          if (err) {
            logError(err);
            res.json(503, "Service Unavailable");
            return;
          }
          res.json(200, {cert: cert, publicKey: _publicKey.serialize()});
        });
      });
    });
  });

/**
 * Ask for a new verification code.
 **/
app.post("/sms/mt/resend_code", hawkMiddleware, requireParams("msisdn"),
  validateMSISDN, function(req, res) {
    var code = digitsCode(DIGIT_CODE_SIZE);

    storage.setCode(req.msisdnId, code, function(err) {
      if (err) {
        logError(err);
        res.json(503, "Service Unavailable");
        return;
      }
      /* Send SMS */
      smsGateway.sendSMS(req.msisdn,
        "To validate your number please enter the following code: " + code,
        function(err) {
          res.json(200, {});
        });
    });
  });

app.listen(conf.get("port"), conf.get("host"), function(){
  console.log("Server listening on http://" +
              conf.get("host") + ":" + conf.get("port"));
});

module.exports = {
  app: app,
  conf: conf,
  storage: storage,
  requireParams: requireParams
};
<|endoftext|>


<commit_before>'use strict';

import Vector from 'core/Vector'
import { setCam, addLine, removeLine, replaceLine, pushAction, selectLine } from './actions';

export function debugTool(stream, dispatch, getState) {
  stream.first().subscribe(pos => {
    console.log('start', pos.x, pos.y);
  });
  return {
    stream: stream.skip(1),
    onNext: () => console.log('move'),
    onCancel: () => console.log('cancel'),
    onEnd: () => console.log('end')
  };
}

export function pan(stream, dispatch, getState) {
  var firstPos;
  stream.first().subscribe( pos => {
    firstPos = pos;
  });
  var { x, y, z } = getState().viewport;
  return {
    stream: stream.map((pos) =>
      firstPos.clone().subtract(pos).mulS(z).add({x, y})
    ),
    onNext: (trackPos) => dispatch(setCam({x: trackPos.x, y: trackPos.y, z})),
    onCancel: () => dispatch(setCam({x, y, z}))
  };

}

function getAbsPos(relPos, getState) {
  var { viewport: {w, h, x, y, z} } = getState();
  return relPos.clone().subtract({x: w/2, y: h/2}).mulS(z).add({x, y});
}

const MAX_SNAP_DISTANCE = 8
function getSnappedPos(absPos, getState, ignoreLineID) {
  var { trackData: {track}, viewport: {z} } = getState();

  // adjust snap radius to current zoom level
  var maxSnap = MAX_SNAP_DISTANCE * Math.max(z, ZOOM.MIN * 10)

  return track.getLinesInRadius(absPos.x, absPos.y, maxSnap)
    .filter(line =>
      // skip our ignoreline if given
      // because it represents the current line we're drawing
      line.id !== ignoreLineID
    )
    .reduce((points, line) =>
      // create array of points from array of lines
      points.concat([line.p, line.q])
    , [])
    .reduce(([snapPos, closestDistance], point) => {
      // reduce array of points to the point closest to absPos within maxSnap
      let distance = absPos.distance(point)
      return distance < closestDistance
        ? [point, distance]
        : [snapPos, closestDistance]
    }, [absPos, maxSnap])[0]
    .clone() // vectors are mutable, defensively copy them
}

const ZOOM = {
  STRENGTH: Math.pow(2, 1/(2<<5)),
  MAX: 2<<4,
  MIN: 1/(2<<4)
};
function getZoom(z, delta) {
  let dz = Math.pow(ZOOM.STRENGTH, delta);
  return Math.min(Math.max(z / dz, ZOOM.MIN), ZOOM.MAX);
}
function getPosFromZoom(pos, center, zoom, initZoom) {
  return pos.clone().mulS(-1).add(center).mulS(zoom / initZoom).add(pos)
}
export function zoom(stream, dispatch, getState) {
  var firstPos, y0;
  var { viewport: {x, y, z} } = getState();
  stream.first().subscribe( pos => {
    y0 = pos.y;
    firstPos = getAbsPos(pos, getState);
  });
  return {
    stream: stream.map(pos => getZoom(z, y0 - pos.y)),
    onNext: (zoom) => {
      let pos = getPosFromZoom(firstPos, {x, y}, zoom, z)
      dispatch(setCam({x: pos.x, y: pos.y, z: zoom}));
    },
    onCancel: () => dispatch(setCam({x, y, z}))
  };

}

export function deltaPanModZoom(pos, delta, dispatch, getState) {
  var { viewport: {x, y, z}, modKeys: {mod} } = getState();
  let newPos
  let zoom = z
  if (mod) { // zoom
    zoom = getZoom(z, delta.y)
    newPos = getPosFromZoom(getAbsPos(pos, getState), {x, y}, zoom, z)
  } else { // pan
    newPos = delta.mulS(z).add({x, y})
  }
  dispatch(setCam({
    x: newPos.x,
    y: newPos.y,
    z: zoom
  }))
}

// TODO: round according to zoom factor
const ANGLE_INC = 15
function angleSnap(pnt, pos) {
  let delta = pnt.clone().subtract(pos)
  let angle = Math.atan2(delta.y, delta.x) / Math.PI * 180
  angle = Math.round(angle / ANGLE_INC) * ANGLE_INC
  let [x, y] = (() => {
    switch (angle) {
      case 360:
      case 0:
        return [1, 0]
      case 90:
        return [0, 1]
      case 180:
        return [-1, 0]
      case 270:
        return [0, -1]
      default:
        let rads = angle / 180 * Math.PI
        return [Math.cos(rads), Math.sin(rads)]
    }
  })()
  return (new Vector(x, y)).mulS(delta.dot({x, y})).add(pos)
}

// TODO: put ID management in reducer
// TODO: make minimum line length depend on zoom
const MIN_LINE_LENGTH = 3;
var tempID = 0;
export function line(stream, dispatch, getState) {
  let p1
  let prevLine = null
  let id = tempID++; // TODO: make addLine responsible for getting actual ID!
  // TODO: wrap functions around mod keys for clarity
  stream = stream.map(pos => {
    let absPos = getAbsPos(pos, getState)
    let {modKeys: {alt}} = getState()
    if (!alt) {
      return getSnappedPos(absPos, getState, prevLine ? prevLine.id : null)
    }
    return absPos
  })

  stream.first().subscribe( pos => {
    p1 = pos
  });
  let {modKeys: {shift}} = getState()

  return {
    stream: stream
      .filter(p2 => p1.distance(p2) >= MIN_LINE_LENGTH),
    onNext: (p2) => {
      let {toolbars: {colorSelected: lineType}, modKeys: {mod}} = getState()
      if (mod) {
        p2 = angleSnap(p2, p1)
      }
      let lineData = {
        x1: p1.x,
        y1: p1.y,
        x2: p2.x,
        y2: p2.y,
        id: id,
        flipped: shift,
        type: lineType
      }
      let action
      if (prevLine) {
        action = replaceLine(prevLine, lineData)
      } else {
        action = addLine(lineData)
      }
      dispatch(action)
      prevLine = lineData
    },
    onEnd: () => {
      if (prevLine) {
        dispatch(pushAction(addLine(prevLine)))
      }
    },
    onCancel: () => {
      if (prevLine) {
        dispatch(removeLine(prevLine))
      }
    }
  }
}
export function pencil(stream, dispatch, getState) {
  stream = stream.map((pos) => getAbsPos(pos, getState))
  let p0
  let addedLines = []
  stream.first().subscribe( pos => {
    p0 = pos
  });
  return {
    stream: stream
      .scan(([prevLine, p1], p2) => {
        if (p1.distance(p2) >= MIN_LINE_LENGTH) {
          return [[p1, p2], p2]
        }
        return [null, p1]
      }, [null, p0])
      .map(([prevLine, p1]) => prevLine)
      .filter(prevLine => prevLine !== null),
    onNext: ([p1, p2]) => {
      let {toolbars: {colorSelected: lineType}, modKeys: {shift}} = getState()
      let lineData = {
        x1: p1.x,
        y1: p1.y,
        x2: p2.x,
        y2: p2.y,
        flipped: shift,
        id: tempID++,
        type: lineType
      }
      dispatch(addLine(lineData))
      addedLines.push(lineData);
    },
    onEnd: () => {
      if (addedLines.length > 0) {
        dispatch(pushAction(addLine(addedLines)))
      }
    },
    onCancel: () => {
      dispatch(removeLine(addedLines))
    }
  }
}

const ERASER_RADIUS = 2;
export function eraser(stream, dispatch, getState, cancellableStream) {
  var removedLines = [];
  return {
    stream: cancellableStream.map((pos) => getAbsPos(pos, getState)),
    onNext: (pos) => {
      let linesToRemove = getState().trackData.track.getLinesInRadius(pos.x, pos.y, ERASER_RADIUS)
      removedLines = removedLines.concat(linesToRemove);
      dispatch(removeLine(linesToRemove));
    },
    onEnd: () => {
      if (removedLines.length > 0) {
        dispatch(pushAction(removeLine(removedLines)))
      }
    },
    onCancel: () => {
      dispatch(addLine(removedLines))
    }
  }
}

const DragTypes = {
  P: 1,
  Q: 2,
  LINE: 3 // bit flags ok???
}
const SELECTION_RADIUS = 10
function getDragType(pos, line, radius) {
  if (line.vec.clone().mulS(0.5).add(line.p).distance(pos) < (radius / 2)) {
    return DragTypes.LINE
  }
  let pDist = line.p.distance(pos)
  let qDist = line.q.distance(pos)
  if (pDist > radius && qDist > radius) {
    return DragTypes.LINE
  }
  if (pDist < qDist) {
    return DragTypes.P
  } else {
    return DragTypes.Q
  }
}

// TODO: separate into separate streams and clean up
export function select(stream, dispatch, getState, cancellableStream) {
  let firstPos
  let selectedLineID = null
  let prevLine = null
  let modifyingLine = null
  let dragType
  let {trackData: {track}, viewport: {z}, lineSelection: {lineID: prevSelectedLineID}} = getState()
  let radius = SELECTION_RADIUS * z
  stream = cancellableStream.map((pos) => getAbsPos(pos, getState))
  stream.first().subscribe(pos => {
    firstPos = pos
    if (prevSelectedLineID != null) {
      let line = track.getLineByID(prevSelectedLineID)
      if (line && line.inRadius(pos.x, pos.y, radius)) {
        modifyingLine = line
        prevLine = line
        dragType = getDragType(pos, line, radius)
        selectedLineID = prevSelectedLineID
        return
      }
    }
    let selectedLine = track.getLinesInRadius(pos.x, pos.y, radius)[0]
    if (selectedLine) {
      selectedLineID = selectedLine.id
    }
  })
  return {
    stream: stream.skip(1),
    onNext: (pos) => {
      if (modifyingLine != null) {
        let {modKeys: {mod, alt}} = getState()
        let delta = pos.clone().subtract(firstPos)
        let {p, q} = modifyingLine
        if (dragType & DragTypes.P) {
          p = p.clone().add(delta)
        }
        if (dragType & DragTypes.Q) {
          q = q.clone().add(delta)
        }
        if (mod) {
          if (dragType === DragTypes.P) {
            p = angleSnap(p, q)
          }
          if (dragType === DragTypes.Q) {
            q = angleSnap(q, p)
          }
        }
        if (!alt && !mod) {
          let snap
          if (dragType === DragTypes.P) {
            snap = getSnappedPos(p, getState, selectedLineID)
            if (!snap.equals(q)) {
              p = snap
            }
          }
          if (dragType === DragTypes.Q) {
            snap = getSnappedPos(q, getState, selectedLineID)
            if (!snap.equals(p)) {
              q = snap
            }
          }
        }
        if (p.equals(q)) {
          return
        }
        let draggedLine = modifyingLine.setPoints(p, q)
        dispatch(replaceLine(prevLine, draggedLine))
        prevLine = draggedLine
      }
    },
    onEnd: () => {
      if (modifyingLine != null) {
        if (modifyingLine.equals(prevLine)) {
          return;
        }
        dispatch(pushAction(replaceLine(modifyingLine, prevLine)))
      } else {
        dispatch(selectLine(selectedLineID))
      }
    },
    onCancel: () => {
      if (modifyingLine != null) {
        dispatch(replaceLine(prevLine, modifyingLine))
      }
    }
  }
}
<commit_msg>reduce minimum line length<commit_after>'use strict';

import Vector from 'core/Vector'
import { setCam, addLine, removeLine, replaceLine, pushAction, selectLine } from './actions';

export function debugTool(stream, dispatch, getState) {
  stream.first().subscribe(pos => {
    console.log('start', pos.x, pos.y);
  });
  return {
    stream: stream.skip(1),
    onNext: () => console.log('move'),
    onCancel: () => console.log('cancel'),
    onEnd: () => console.log('end')
  };
}

export function pan(stream, dispatch, getState) {
  var firstPos;
  stream.first().subscribe( pos => {
    firstPos = pos;
  });
  var { x, y, z } = getState().viewport;
  return {
    stream: stream.map((pos) =>
      firstPos.clone().subtract(pos).mulS(z).add({x, y})
    ),
    onNext: (trackPos) => dispatch(setCam({x: trackPos.x, y: trackPos.y, z})),
    onCancel: () => dispatch(setCam({x, y, z}))
  };

}

function getAbsPos(relPos, getState) {
  var { viewport: {w, h, x, y, z} } = getState();
  return relPos.clone().subtract({x: w/2, y: h/2}).mulS(z).add({x, y});
}

const MAX_SNAP_DISTANCE = 8
function getSnappedPos(absPos, getState, ignoreLineID) {
  var { trackData: {track}, viewport: {z} } = getState();

  // adjust snap radius to current zoom level
  var maxSnap = MAX_SNAP_DISTANCE * Math.max(z, ZOOM.MIN * 10)

  return track.getLinesInRadius(absPos.x, absPos.y, maxSnap)
    .filter(line =>
      // skip our ignoreline if given
      // because it represents the current line we're drawing
      line.id !== ignoreLineID
    )
    .reduce((points, line) =>
      // create array of points from array of lines
      points.concat([line.p, line.q])
    , [])
    .reduce(([snapPos, closestDistance], point) => {
      // reduce array of points to the point closest to absPos within maxSnap
      let distance = absPos.distance(point)
      return distance < closestDistance
        ? [point, distance]
        : [snapPos, closestDistance]
    }, [absPos, maxSnap])[0]
    .clone() // vectors are mutable, defensively copy them
}

const ZOOM = {
  STRENGTH: Math.pow(2, 1/(2<<5)),
  MAX: 2<<4,
  MIN: 1/(2<<4)
};
function getZoom(z, delta) {
  let dz = Math.pow(ZOOM.STRENGTH, delta);
  return Math.min(Math.max(z / dz, ZOOM.MIN), ZOOM.MAX);
}
function getPosFromZoom(pos, center, zoom, initZoom) {
  return pos.clone().mulS(-1).add(center).mulS(zoom / initZoom).add(pos)
}
export function zoom(stream, dispatch, getState) {
  var firstPos, y0;
  var { viewport: {x, y, z} } = getState();
  stream.first().subscribe( pos => {
    y0 = pos.y;
    firstPos = getAbsPos(pos, getState);
  });
  return {
    stream: stream.map(pos => getZoom(z, y0 - pos.y)),
    onNext: (zoom) => {
      let pos = getPosFromZoom(firstPos, {x, y}, zoom, z)
      dispatch(setCam({x: pos.x, y: pos.y, z: zoom}));
    },
    onCancel: () => dispatch(setCam({x, y, z}))
  };

}

export function deltaPanModZoom(pos, delta, dispatch, getState) {
  var { viewport: {x, y, z}, modKeys: {mod} } = getState();
  let newPos
  let zoom = z
  if (mod) { // zoom
    zoom = getZoom(z, delta.y)
    newPos = getPosFromZoom(getAbsPos(pos, getState), {x, y}, zoom, z)
  } else { // pan
    newPos = delta.mulS(z).add({x, y})
  }
  dispatch(setCam({
    x: newPos.x,
    y: newPos.y,
    z: zoom
  }))
}

// TODO: round according to zoom factor
const ANGLE_INC = 15
function angleSnap(pnt, pos) {
  let delta = pnt.clone().subtract(pos)
  let angle = Math.atan2(delta.y, delta.x) / Math.PI * 180
  angle = Math.round(angle / ANGLE_INC) * ANGLE_INC
  let [x, y] = (() => {
    switch (angle) {
      case 360:
      case 0:
        return [1, 0]
      case 90:
        return [0, 1]
      case 180:
        return [-1, 0]
      case 270:
        return [0, -1]
      default:
        let rads = angle / 180 * Math.PI
        return [Math.cos(rads), Math.sin(rads)]
    }
  })()
  return (new Vector(x, y)).mulS(delta.dot({x, y})).add(pos)
}

// TODO: put ID management in reducer
// TODO: make minimum line length depend on zoom
const MIN_LINE_LENGTH = 1;
var tempID = 0;
export function line(stream, dispatch, getState) {
  let p1
  let prevLine = null
  let id = tempID++; // TODO: make addLine responsible for getting actual ID!
  // TODO: wrap functions around mod keys for clarity
  stream = stream.map(pos => {
    let absPos = getAbsPos(pos, getState)
    let {modKeys: {alt}} = getState()
    if (!alt) {
      return getSnappedPos(absPos, getState, prevLine ? prevLine.id : null)
    }
    return absPos
  })

  stream.first().subscribe( pos => {
    p1 = pos
  });
  let {modKeys: {shift}} = getState()

  return {
    stream: stream
      .filter(p2 => p1.distance(p2) >= MIN_LINE_LENGTH),
    onNext: (p2) => {
      let {toolbars: {colorSelected: lineType}, modKeys: {mod}} = getState()
      if (mod) {
        p2 = angleSnap(p2, p1)
      }
      let lineData = {
        x1: p1.x,
        y1: p1.y,
        x2: p2.x,
        y2: p2.y,
        id: id,
        flipped: shift,
        type: lineType
      }
      let action
      if (prevLine) {
        action = replaceLine(prevLine, lineData)
      } else {
        action = addLine(lineData)
      }
      dispatch(action)
      prevLine = lineData
    },
    onEnd: () => {
      if (prevLine) {
        dispatch(pushAction(addLine(prevLine)))
      }
    },
    onCancel: () => {
      if (prevLine) {
        dispatch(removeLine(prevLine))
      }
    }
  }
}
export function pencil(stream, dispatch, getState) {
  stream = stream.map((pos) => getAbsPos(pos, getState))
  let p0
  let addedLines = []
  stream.first().subscribe( pos => {
    p0 = pos
  });
  return {
    stream: stream
      .scan(([prevLine, p1], p2) => {
        if (p1.distance(p2) >= MIN_LINE_LENGTH) {
          return [[p1, p2], p2]
        }
        return [null, p1]
      }, [null, p0])
      .map(([prevLine, p1]) => prevLine)
      .filter(prevLine => prevLine !== null),
    onNext: ([p1, p2]) => {
      let {toolbars: {colorSelected: lineType}, modKeys: {shift}} = getState()
      let lineData = {
        x1: p1.x,
        y1: p1.y,
        x2: p2.x,
        y2: p2.y,
        flipped: shift,
        id: tempID++,
        type: lineType
      }
      dispatch(addLine(lineData))
      addedLines.push(lineData);
    },
    onEnd: () => {
      if (addedLines.length > 0) {
        dispatch(pushAction(addLine(addedLines)))
      }
    },
    onCancel: () => {
      dispatch(removeLine(addedLines))
    }
  }
}

const ERASER_RADIUS = 2;
export function eraser(stream, dispatch, getState, cancellableStream) {
  var removedLines = [];
  return {
    stream: cancellableStream.map((pos) => getAbsPos(pos, getState)),
    onNext: (pos) => {
      let linesToRemove = getState().trackData.track.getLinesInRadius(pos.x, pos.y, ERASER_RADIUS)
      removedLines = removedLines.concat(linesToRemove);
      dispatch(removeLine(linesToRemove));
    },
    onEnd: () => {
      if (removedLines.length > 0) {
        dispatch(pushAction(removeLine(removedLines)))
      }
    },
    onCancel: () => {
      dispatch(addLine(removedLines))
    }
  }
}

const DragTypes = {
  P: 1,
  Q: 2,
  LINE: 3 // bit flags ok???
}
const SELECTION_RADIUS = 10
function getDragType(pos, line, radius) {
  if (line.vec.clone().mulS(0.5).add(line.p).distance(pos) < (radius / 2)) {
    return DragTypes.LINE
  }
  let pDist = line.p.distance(pos)
  let qDist = line.q.distance(pos)
  if (pDist > radius && qDist > radius) {
    return DragTypes.LINE
  }
  if (pDist < qDist) {
    return DragTypes.P
  } else {
    return DragTypes.Q
  }
}

// TODO: separate into separate streams and clean up
export function select(stream, dispatch, getState, cancellableStream) {
  let firstPos
  let selectedLineID = null
  let prevLine = null
  let modifyingLine = null
  let dragType
  let {trackData: {track}, viewport: {z}, lineSelection: {lineID: prevSelectedLineID}} = getState()
  let radius = SELECTION_RADIUS * z
  stream = cancellableStream.map((pos) => getAbsPos(pos, getState))
  stream.first().subscribe(pos => {
    firstPos = pos
    if (prevSelectedLineID != null) {
      let line = track.getLineByID(prevSelectedLineID)
      if (line && line.inRadius(pos.x, pos.y, radius)) {
        modifyingLine = line
        prevLine = line
        dragType = getDragType(pos, line, radius)
        selectedLineID = prevSelectedLineID
        return
      }
    }
    let selectedLine = track.getLinesInRadius(pos.x, pos.y, radius)[0]
    if (selectedLine) {
      selectedLineID = selectedLine.id
    }
  })
  return {
    stream: stream.skip(1),
    onNext: (pos) => {
      if (modifyingLine != null) {
        let {modKeys: {mod, alt}} = getState()
        let delta = pos.clone().subtract(firstPos)
        let {p, q} = modifyingLine
        if (dragType & DragTypes.P) {
          p = p.clone().add(delta)
        }
        if (dragType & DragTypes.Q) {
          q = q.clone().add(delta)
        }
        if (mod) {
          if (dragType === DragTypes.P) {
            p = angleSnap(p, q)
          }
          if (dragType === DragTypes.Q) {
            q = angleSnap(q, p)
          }
        }
        if (!alt && !mod) {
          let snap
          if (dragType === DragTypes.P) {
            snap = getSnappedPos(p, getState, selectedLineID)
            if (!snap.equals(q)) {
              p = snap
            }
          }
          if (dragType === DragTypes.Q) {
            snap = getSnappedPos(q, getState, selectedLineID)
            if (!snap.equals(p)) {
              q = snap
            }
          }
        }
        if (p.equals(q)) {
          return
        }
        let draggedLine = modifyingLine.setPoints(p, q)
        dispatch(replaceLine(prevLine, draggedLine))
        prevLine = draggedLine
      }
    },
    onEnd: () => {
      if (modifyingLine != null) {
        if (modifyingLine.equals(prevLine)) {
          return;
        }
        dispatch(pushAction(replaceLine(modifyingLine, prevLine)))
      } else {
        dispatch(selectLine(selectedLineID))
      }
    },
    onCancel: () => {
      if (modifyingLine != null) {
        dispatch(replaceLine(prevLine, modifyingLine))
      }
    }
  }
}
<|endoftext|>


<commit_before>const path = require('path');

const {Toolkit} = require('actions-toolkit');
const fetchSchema = require('./fetch-schema');

const schemaUpdateLabel = {
  name: 'schema update',
  id: 'MDU6TGFiZWwxMzQyMzM1MjQ2',
};

Toolkit.run(async tools => {
  await tools.runInWorkspace('git', ['config', '--global', 'user.email', 'hubot@github.com']);
  await tools.runInWorkspace('git', ['config', '--global', 'user.name', 'hubot']);

  tools.log.info('Fetching the latest GraphQL schema changes.');
  await fetchSchema();

  const {code: hasSchemaChanges} = await tools.runInWorkspace(
    'git', ['diff', '--quiet', '--', 'graphql/schema.graphql'],
    {reject: false},
  );
  if (hasSchemaChanges === 0) {
    tools.log.info('No schema changes to fetch.');
    tools.exit.neutral('Nothing to do.');
  }

  tools.log.info('Committing schema changes.');
  await tools.runInWorkspace('git', ['commit', '--all', '--message', ':arrow_up: GraphQL schema']);

  tools.log.info('Re-running relay compiler.');
  const {failed: relayFailed, stderr: relayError, stdout: relayOutput} = await tools.runInWorkspace(
    path.resolve(__dirname, 'node_modules', '.bin', 'relay-compiler'),
    ['--watchman', 'false', '--src', './lib', '--schema', 'graphql/schema.graphql'],
    {reject: false},
  );
  tools.log.info('Relay output:\n%s', relayOutput);

  const {code: hasRelayChanges} = await tools.runInWorkspace(
    'git', ['diff', '--quiet'],
    {reject: false},
  );

  if (hasRelayChanges === 0 && !relayFailed) {
    tools.log.info('Generated relay files are unchanged.');
    await tools.runInWorkspace('git', ['push']);
    tools.exit.success('Schema is up to date on master.');
  }

  tools.log.info('Checking for unmerged schema update pull requests.');
  const openPullRequestsQuery = await tools.github.graphql(`
    query openPullRequestsQuery($owner: String!, $repo: String!, $labelName: String!) {
      repository(owner: $owner, name: $repo) {
        id
        pullRequests(first: 1, states: [OPEN], labels: [$labelName]) {
          totalCount
        }
      }
    }
  `, {...tools.context.repo, labelName: schemaUpdateLabel.name});

  const repositoryId = openPullRequestsQuery.repository.id;

  if (openPullRequestsQuery.repository.pullRequests.totalCount > 0) {
    tools.exit.neutral('One or more schema update pull requests are already open. Please resolve those first.');
  }

  const branchName = `schema-update/${Date.now()}`;
  tools.log.info(`Commiting relay-compiler changes to a new branch ${branchName}.`);
  await tools.runInWorkspace('git', ['checkout', '-b', branchName]);
  if (!relayFailed) {
    await tools.runInWorkspace('git', ['commit', '--all', '--message=":gear: relay-compiler changes"']);
  }
  await tools.runInWorkspace('git', ['push', 'origin', branchName]);

  tools.log.info('Creating a pull request.');

  let body = `:robot: _This automated pull request brought to you by [a GitHub action](/actions/schema-up)_ :robot:

The GraphQL schema has been automatically updated and \`relay-compiler\` has been re-run on the package source.`;

  if (!relayFailed) {
    body += ' The modified files have been committed to this branch and pushed. ';
    body += 'If all of the tests pass in CI, merge with confidence :zap:';
  } else {
    body += ' `relay-compiler` failed with the following output:\n\n```\n';
    body += relayError;
    body += '\n```\n\nCheck out this branch to fix things so we don\'t break.';
  }

  const createPullRequestMutation = await tools.github.graphql(`
    mutation createPullRequestMutation($repositoryId: ID!, $headRefName: String!, $body: String!) {
      createPullRequest(input: {
        repositoryId: $repositoryId
        title: "GraphQL schema update"
        body: $body
        baseRefName: "master"
        headRefName: $headRefName
      }) {
        pullRequest {
          id
          number
        }
      }
    }
  `, {
    repositoryId,
    headRefName: branchName,
    body,
  });

  const createdPullRequest = createPullRequestMutation.createPullRequest.pullRequest;
  tools.log.info(
    `Pull request #${createdPullRequest.number} has been opened with the changes from this schema upgrade.`,
  );

  await tools.github.graphql(`
    mutation labelPullRequestMutation($id: ID!, $labelIDs: [ID!]!) {
      addLabelsToLabelable(input: {
        labelableId: $id,
        labelIds: $labelIDs
      }) {
        clientMutationId
      }
    }
  `, {id: createdPullRequest.id, labelIDs: [schemaUpdateLabel.id]});
  tools.exit.success(
    `Pull request #${createdPullRequest.number} has been opened and labelled for this schema upgrade.`,
  );
}, {
  secrets: ['GITHUB_TOKEN'],
});
<commit_msg>Specify the upstream to push to<commit_after>const path = require('path');

const {Toolkit} = require('actions-toolkit');
const fetchSchema = require('./fetch-schema');

const schemaUpdateLabel = {
  name: 'schema update',
  id: 'MDU6TGFiZWwxMzQyMzM1MjQ2',
};

Toolkit.run(async tools => {
  await tools.runInWorkspace('git', ['config', '--global', 'user.email', 'hubot@github.com']);
  await tools.runInWorkspace('git', ['config', '--global', 'user.name', 'hubot']);

  tools.log.info('Fetching the latest GraphQL schema changes.');
  await fetchSchema();

  const {code: hasSchemaChanges} = await tools.runInWorkspace(
    'git', ['diff', '--quiet', '--', 'graphql/schema.graphql'],
    {reject: false},
  );
  if (hasSchemaChanges === 0) {
    tools.log.info('No schema changes to fetch.');
    tools.exit.neutral('Nothing to do.');
  }

  tools.log.info('Committing schema changes.');
  await tools.runInWorkspace('git', ['commit', '--all', '--message', ':arrow_up: GraphQL schema']);

  tools.log.info('Re-running relay compiler.');
  const {failed: relayFailed, stderr: relayError, stdout: relayOutput} = await tools.runInWorkspace(
    path.resolve(__dirname, 'node_modules', '.bin', 'relay-compiler'),
    ['--watchman', 'false', '--src', './lib', '--schema', 'graphql/schema.graphql'],
    {reject: false},
  );
  tools.log.info('Relay output:\n%s', relayOutput);

  const {code: hasRelayChanges} = await tools.runInWorkspace(
    'git', ['diff', '--quiet'],
    {reject: false},
  );

  if (hasRelayChanges === 0 && !relayFailed) {
    tools.log.info('Generated relay files are unchanged.');
    const upstream = tools.context.ref.replace(/^refs\/heads\//, '');
    await tools.runInWorkspace('git', ['push', 'origin', upstream]);
    tools.exit.success('Schema is up to date on master.');
  }

  tools.log.info('Checking for unmerged schema update pull requests.');
  const openPullRequestsQuery = await tools.github.graphql(`
    query openPullRequestsQuery($owner: String!, $repo: String!, $labelName: String!) {
      repository(owner: $owner, name: $repo) {
        id
        pullRequests(first: 1, states: [OPEN], labels: [$labelName]) {
          totalCount
        }
      }
    }
  `, {...tools.context.repo, labelName: schemaUpdateLabel.name});

  const repositoryId = openPullRequestsQuery.repository.id;

  if (openPullRequestsQuery.repository.pullRequests.totalCount > 0) {
    tools.exit.neutral('One or more schema update pull requests are already open. Please resolve those first.');
  }

  const branchName = `schema-update/${Date.now()}`;
  tools.log.info(`Commiting relay-compiler changes to a new branch ${branchName}.`);
  await tools.runInWorkspace('git', ['checkout', '-b', branchName]);
  if (!relayFailed) {
    await tools.runInWorkspace('git', ['commit', '--all', '--message=":gear: relay-compiler changes"']);
  }
  await tools.runInWorkspace('git', ['push', 'origin', branchName]);

  tools.log.info('Creating a pull request.');

  let body = `:robot: _This automated pull request brought to you by [a GitHub action](/actions/schema-up)_ :robot:

The GraphQL schema has been automatically updated and \`relay-compiler\` has been re-run on the package source.`;

  if (!relayFailed) {
    body += ' The modified files have been committed to this branch and pushed. ';
    body += 'If all of the tests pass in CI, merge with confidence :zap:';
  } else {
    body += ' `relay-compiler` failed with the following output:\n\n```\n';
    body += relayError;
    body += '\n```\n\nCheck out this branch to fix things so we don\'t break.';
  }

  const createPullRequestMutation = await tools.github.graphql(`
    mutation createPullRequestMutation($repositoryId: ID!, $headRefName: String!, $body: String!) {
      createPullRequest(input: {
        repositoryId: $repositoryId
        title: "GraphQL schema update"
        body: $body
        baseRefName: "master"
        headRefName: $headRefName
      }) {
        pullRequest {
          id
          number
        }
      }
    }
  `, {
    repositoryId,
    headRefName: branchName,
    body,
  });

  const createdPullRequest = createPullRequestMutation.createPullRequest.pullRequest;
  tools.log.info(
    `Pull request #${createdPullRequest.number} has been opened with the changes from this schema upgrade.`,
  );

  await tools.github.graphql(`
    mutation labelPullRequestMutation($id: ID!, $labelIDs: [ID!]!) {
      addLabelsToLabelable(input: {
        labelableId: $id,
        labelIds: $labelIDs
      }) {
        clientMutationId
      }
    }
  `, {id: createdPullRequest.id, labelIDs: [schemaUpdateLabel.id]});
  tools.exit.success(
    `Pull request #${createdPullRequest.number} has been opened and labelled for this schema upgrade.`,
  );
}, {
  secrets: ['GITHUB_TOKEN'],
});
<|endoftext|>


<commit_before>//convert time to military time
var parseMilitaryTime = function(time) {
  parsedTime = time.split(" ")
  parsedTime[0] = parsedTime[0].replace(':', '')
  mTime = parseInt(parsedTime[0])
  if (parsedTime[1] == "pm") {
    mTime += 1200
  }

  return mTime
}

//check whether two given timeframes have no overlap by using military time
//if clash, return true
var detectTimeClash = function (startTime1, endTime1, startTime2, endTime2) {
  startTime1m = parseMilitaryTime(startTime1)
  endTime1m = parseMilitaryTime(endTime1)
  startTime2m = parseMilitaryTime(startTime2)
  endTime2m = parseMilitaryTime(endTime2)

  //start or end at same time
  if (startTime1m == startTime2m || endTime1m == endTime2m) {
    return true
  }

  //check if time2 begins before time1 ends
  if (startTime1m < startTime2m) {
    if (startTime2m < endTime1m) {
      return true
    }
  }

  //check if time1 begins before time2 ends
  else {
    if (startTime1m < endTime2m) {
      return true
    }
  }

  //classes do not overlap
  return false
}

var detectCourseClash = function (favoriteCourses, courses, excludeClashingCourses) {
  // check exclude boolean,

  //if no courses are favorited, no clashes can exist
  if (favoriteCourses == []) {
    return courses
  }

  //since favorite courses exist, do initial clash detection within them

  for (var courseIndex in courses) {
    var thisCourse = courses[courseIndex]

    if (thisCourse.title === 'American Economic History') {
      thisCourse.clash = {
        clash: true,
        with: 'Math 202'
      }
    }

    courses[courseIndex] = thisCourse
  }

  return 2017
}

module.exports.detectCourseClash = detectCourseClash
<commit_msg>Added data structure implementation for clash detection<commit_after>//convert time to military time
var parseMilitaryTime = function(time) {
  parsedTime = time.split(" ")
  parsedTime[0] = parsedTime[0].replace(':', '')
  mTime = parseInt(parsedTime[0])
  if (parsedTime[1] == "pm") {
    mTime += 1200
  }

  return mTime
}

//check whether two given timeframes have no overlap by using military time
//if clash, return true
var detectTimeClash = function (startTime1, endTime1, startTime2, endTime2) {
  if (startTime1 == null || endTime1 == null || startTime2 == null || endTime2 == null) {
    return false
  }
  startTime1m = parseMilitaryTime(startTime1)
  endTime1m = parseMilitaryTime(endTime1)
  startTime2m = parseMilitaryTime(startTime2)
  endTime2m = parseMilitaryTime(endTime2)

  //start or end at same time
  if (startTime1m == startTime2m || endTime1m == endTime2m) {
    return true
  }

  //check if time2 begins before time1 ends
  if (startTime1m < startTime2m) {
    if (startTime2m < endTime1m) {
      return true
    }
  }

  //check if time1 begins before time2 ends
  else {
    if (startTime1m < endTime2m) {
      return true
    }
  }

  //classes do not overlap
  return false
}

var detectSectionClash = function(section1, section2) {
  if (section1 == null || section2 == null) {
    return false
  }
  //replace Th with Z to help with substring find
  days1 = section1days.replace("Th", "Z")
  days2 = section2days.replace("Th", "Z")
  if (days1 != null && days2 != null) {
    for (var i = 0; i < days1.length; i++) {
      if (days2.includes(days1.charAt(i))) {
        return detectTimeClash(section1.starttime, section1.endtime, section2.starttime, section2.endtime)
      }
    }
  }
  return false
}

/* Seb, if you're reading this, I want you to know, I decided to not use a graph
for this because we would have to implement all the aspects of graphs here
(data structures and algorithms) and it's not more time efficient than doing the
same work with nested arrays. This is because the possible no-clash paths are not
something that can be passed on efficiently once the original analysis for
favoriteCourses happens, so clash detection would still have to be done for everything
due to the nature of the graphs that would be passed on.
Thus, I'm doing nested arrays with some additional arrays to hold indices of the sub-
arrays. (I know this is complicated, but that's what happens with a problem that's
exponential in nature.) Hopefully, this will all work, and fast enough that it doesn't
have to make a lot of sense. */

var detectCourseClash = function (favoriteCourses, courses, excludeClashingCourses) {

  //if no courses are favorited, no clashes can exist
  if (favoriteCourses == []) {
    return courses
  }

  //since favorite courses exist, do initial clash detection within them
  //WARNING: THIS HINGES ON THE SECTIONS BEING ORDERED BY SECTION TYPE

  //populate all the sections of all the favorite courses

  var favCourseSectionsAll = [] //holds all the sections
  var favCourseSectionsInc = [] //holds whether a section can exist in a non-clash schedule
  var maxLength = [] //lengths of nested arrays in favCourseSectionsAll
  var sectionIndex = [] //index to be used when looking at possible schedules

  for (var course in favoriteCourses) {
    //each course has new, independent sections
    var prevSectionType = null
    var courseSectionsAll = []
    var courseSectionsInc = []
    var maxLengthIndex = 0
    var sectionIndex = []

    for (var section in course.classes) {
      if (prevSectionType != null && section.section != null) {
        //new section type within a course, push previous information into arrays
        if (prevSectionType != section.section.charAt(0)) {
          favCourseSectionsAll.push(courseSections)
          courseSectionsAll = []
          favCourseSectionsInc.push(courseSectionsInc)
          courseSectionsInc = []
          maxLength.push(maxLengthIndex)
          maxLengthIndex = 0
          sectionIndex.push(0)

        }
      }
      courseSectionsAll.push(section)
      courseSectionsInc.push(false)
      prevSectionType = section.section.charAt(0)
      maxLengthIndex++
    }

    //all sections seen within course, push information into arrays
    if (courseSectionsAll != null) {
      favCourseSectionsAll.push(courseSectionsAll)
      favCourseSectionsInc.push(courseSectionsInc)
      maxLength.push(maxLengthIndex)
      sectionIndex.push(0)
    }
  }

  //now check for all possible schedules to include only possible sections

  for (var i = 0; i < favCourseSectionsAll.length; i++) {
    for (var j = i-1; j >= 0; j--) {
      sectionIndex[i] = 0
      sectionCurrent = favCourseSectionsAll[i][sectionIndex[i]]
      sectionPrevious = favCourseSectionsAll[j][sectionIndex[j]]
      while (detectSectionClash(sectionCurrent, sectionPrevious)) {
        sectionIndex[i]++
      }
    }
  }

  /* Extra code from when I was foolish enough to include hash tables. (Accessing in
  a nested fashion became a nightmare.)
  var favCoursesCourses = []
  for (course in favoriteCourses) {
    var courseSectionsLists = {}
    for (var section in course.classes) {
      var sectionName = course.classes[classindex].section
      if (courseSectionsLists[sectionName.charAt(0)] == null) {
        courseSectionsLists[sectionName.charAt[0]] = []
      }
      courseSectionsLists[sectionName.charAt[0]].push(section)
    }
    favCoursesCourses.push(courseSectionsLists)
  }
  */

  //now run courseclash to see if any sections can be excluded due to unnegotiable clash with favorites

  for (var courseIndex in courses) {
    var thisCourse = courses[courseIndex]

    if (thisCourse.title === 'American Economic History') {
      thisCourse.clash = {
        clash: true,
        with: 'Math 202'
      }
    }

    courses[courseIndex] = thisCourse
  }

  return 2017
}

module.exports.detectCourseClash = detectCourseClash
<|endoftext|>


<commit_before>aFunction = function(msg, htmlNode){
    //alert(msg);
    var a = document.createElement("a");
    a.href = "www.google.com";
    a.appendChild(document.createTextNode("Soy un link"));
    htmlNode.style.color = "blue";
    htmlNode.appendChild(a);
};

var DecoratorRepository = {
        decorators: [
            {
                type: "Pelicula",
                elements: [
                    {
                        name: "IMDB",
                        functions: [
                            {
                                description: "Puntaje de IMDb" ,
                                method : aFunction
                            },
                            {
                                description: "Premios",
                                method : aFunction
                            },
                            {
                                description: "Ficha de Director",
                                method : aFunction
                            }
                        ],
                        applyTo: function(guiManager, htmlNode){
                            guiManager.drawSubmenu(this, htmlNode);
                        }
                    },
                    {
                        name: "Mercado Libre",
                        functions: [
                            {
                                description: "Buscar Precios",
                                method : aFunction
                            }
                        ]
                    }
                ]
            },
            {
                type: "song",
                elements: []
            }
        ],
        getDecoratorsForConcept: function (aConcept) {
            for (var i = this.decorators.length - 1; i >= 0; i--) {
                if (this.decorators[i].type == aConcept.name) {
                    return this.decorators[i].elements;
                }
            }
        }
    };<commit_msg>Repository de decoradores actualizado<commit_after>function processRequests(data) {
    GM_xmlhttpRequest({
        method: "GET",
        url: data.url,
        headers: {"Accept": "application/json"},
        // synchronous: true,
        // synchronous: false,
        onload: function(response) {
            data.callback(response);
        }
    });
}

function appendData(htmlNode, data){
    var newNode = document.createElement("div");
    newNode.appendChild(data);

    htmlNode.parentNode.insertBefore(newNode, htmlNode.nextSibling);
}

aFunction = function(htmlNode){
    //alert(msg);
    var a = document.createElement("a");
    a.href = "www.google.com";
    a.appendChild(document.createTextNode("Soy un link"));
    htmlNode.style.color = "blue";
    htmlNode.parentNode.insertBefore(a, htmlNode.nextSibling);
    //htmlNode.appendChild(a);
};

var DecoratorRepository = {
        decorators: [
            {
                type: "Movie",
                elements: [
                    {
                        name: "IMDB",
                        functions: [
                            {
                                description: "Puntaje de IMDb" ,
                                method : function imdbRating(htmlNode){
                                    var actionFunction = function(){
                                        appendData(htmlNode, document.createTextNode("Puntaje IMDB: "+htmlNode.conceptImdbData.imdbRating));
                                    }

                                    var getImdbData = function(movieID){
                                        return {
                                            url: 'http://www.omdbapi.com/?i='+movieID+'&plot=short&r=json',
                                            callback: function(response){
                                                var jsonResponse = JSON.parse(response.responseText);

                                                htmlNode.conceptImdbData = jsonResponse;
                                                actionFunction();
                                            }
                                        }
                                    }

                                    var findImdbId = function(title){
                                        return {
                                            url: 'http://www.imdb.com/find?s=tt&ttype=ft&ref_=fn_ft&q='+title,
                                            callback: function (response) {
                                                // Obetner el ID con XPath
                                                var responseXML = new DOMParser().parseFromString(response.responseText, "text/html");
                                                var resultsIDs = responseXML.evaluate('//td[@class="result_text"]/a/@href', responseXML, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
                                                var movieID = resultsIDs.snapshotItem(0).value.substring(7,16);

                                                processRequests(getImdbData(movieID));
                                            }
                                        }
                                    }

                                    if (typeof (htmlNode.conceptImdbData) !== "undefined"){
                                        actionFunction();
                                    } else {
                                        var xpath = htmlNode.conceptProperties[1].xpath;
                                        var htmlObjects = XpathHelper.getSnapshots(xpath, htmlNode);
                                        var title = htmlObjects.snapshotItem(0).data;

                                        processRequests(findImdbId(title));
                                    }
                                }
                            },
                            {
                                description: "Actores",
                                method : function imdbActors(htmlNode){
                                    var actionFunction = function(){
                                        appendData(htmlNode, document.createTextNode("Actores: "+htmlNode.conceptImdbData.Actors));
                                    }

                                    var getImdbData = function(movieID){
                                        return {
                                            url: 'http://www.omdbapi.com/?i='+movieID+'&plot=short&r=json',
                                            callback: function(response){
                                                var jsonResponse = JSON.parse(response.responseText);

                                                htmlNode.conceptImdbData = jsonResponse;
                                                actionFunction();
                                            }
                                        }
                                    }

                                    var findImdbId = function(title){
                                        return {
                                            url: 'http://www.imdb.com/find?s=tt&ttype=ft&ref_=fn_ft&q='+title,
                                            callback: function (response) {
                                                // Obetner el ID con XPath
                                                var responseXML = new DOMParser().parseFromString(response.responseText, "text/html");
                                                var resultsIDs = responseXML.evaluate('//td[@class="result_text"]/a/@href', responseXML, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
                                                var movieID = resultsIDs.snapshotItem(0).value.substring(7,16);

                                                processRequests(getImdbData(movieID));
                                            }
                                        }
                                    }

                                    if (typeof (htmlNode.conceptImdbData) !== "undefined"){
                                        actionFunction();
                                    } else {
                                        var xpath = htmlNode.conceptProperties[1].xpath;
                                        var htmlObjects = XpathHelper.getSnapshots(xpath, htmlNode);
                                        var title = htmlObjects.snapshotItem(0).data;

                                        processRequests(findImdbId(title));
                                    }
                                }
                            },
                            {
                                description: "Título Internacional",
                                method : function imdbInternationalTitle(htmlNode){
                                    var actionFunction = function(){
                                        appendData(htmlNode, document.createTextNode("Título Internacional: "+htmlNode.conceptImdbData.Title));
                                    }

                                    var getImdbData = function(movieID){
                                        return {
                                            url: 'http://www.omdbapi.com/?i='+movieID+'&plot=short&r=json',
                                            callback: function(response){
                                                var jsonResponse = JSON.parse(response.responseText);

                                                htmlNode.conceptImdbData = jsonResponse;
                                                actionFunction();
                                            }
                                        }
                                    }

                                    var findImdbId = function(title){
                                        return {
                                            url: 'http://www.imdb.com/find?s=tt&ttype=ft&ref_=fn_ft&q='+title,
                                            callback: function (response) {
                                                // Obetner el ID con XPath
                                                var responseXML = new DOMParser().parseFromString(response.responseText, "text/html");
                                                var resultsIDs = responseXML.evaluate('//td[@class="result_text"]/a/@href', responseXML, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
                                                var movieID = resultsIDs.snapshotItem(0).value.substring(7,16);

                                                processRequests(getImdbData(movieID));
                                            }
                                        }
                                    }

                                    if (typeof (htmlNode.conceptImdbData) !== "undefined"){
                                        actionFunction();
                                    } else {
                                        var xpath = htmlNode.conceptProperties[1].xpath;
                                        var htmlObjects = XpathHelper.getSnapshots(xpath, htmlNode);
                                        var title = htmlObjects.snapshotItem(0).data;

                                        processRequests(findImdbId(title));
                                    }
                                }
                            }
                        ],
                        applyTo: function(guiManager, htmlNode){
                            guiManager.drawSubmenu(this, htmlNode);
                        }
                    },
                    {
                        name: "Trailer Addict",
                        functions: [
                            {
                                description: "Buscar Video",
                                method : function trailerAddict(htmlNode){
                                    var getTrailer = function(movieID){
                                        return {
                                            url: 'http://api.traileraddict.com/?imdb='+movieID.slice(2),
                                            callback: function(response){
                                                var regex = /v.traileraddict.com\/(\d*)/;
                                                var regexResult = regex.exec(response.responseText);
                                                var trailerId = regexResult[1];

                                                var newNode = document.createElement("iframe");
                                                newNode.setAttribute("src", "http://v.traileraddict.com/"+trailerId);
                                                newNode.setAttribute("height", "270");
                                                newNode.setAttribute("width", "480");

                                                appendData(htmlNode, newNode);
                                            }
                                        }
                                    }

                                    var findImdbId = function(title){
                                        return {
                                            url: 'http://www.imdb.com/find?s=tt&ttype=ft&ref_=fn_ft&q='+title,
                                            callback: function (response) {
                                                // Obetner el ID con XPath
                                                var responseXML = new DOMParser().parseFromString(response.responseText, "text/html");
                                                var resultsIDs = responseXML.evaluate('//td[@class="result_text"]/a/@href', responseXML, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
                                                var movieID = resultsIDs.snapshotItem(0).value.substring(7,16);

                                                processRequests(getTrailer(movieID));
                                            }
                                        }
                                    }

                                    if (typeof (htmlNode.conceptImdbData) !== "undefined"){
                                        processRequests(getTrailer(htmlNode.conceptImdbData.imdbID));
                                    } else {
                                        var xpath = htmlNode.conceptProperties[1].xpath;
                                        var htmlObjects = XpathHelper.getSnapshots(xpath, htmlNode);
                                        var title = htmlObjects.snapshotItem(0).data;

                                        processRequests(findImdbId(title));
                                    }
                                }
                            }
                        ]
                    }
                ]
            },
            {
                type: "song",
                elements: []
            }
        ],
        getDecoratorsForConcept: function (aConcept) {
            for (var i = this.decorators.length - 1; i >= 0; i--) {
                if (this.decorators[i].type == aConcept.name) {
                    return this.decorators[i].elements;
                }
            }
        }
    };<|endoftext|>


<commit_before>const knex = require('knex'),
    Promise = require('bluebird'),
    omit = require('lodash/omit'),
    debug = require('debug')('knex-migrator:database'),
    errors = require('./errors');
const DatabaseInfo = require('@tryghost/database-info');

/**
 * @NOTE: Knex-migrator only supports knex query builder.
 *
 * @param options
 * @returns {Knex.QueryBuilder | Knex}
 */
exports.connect = function connect(options) {
    options = options || {};

    // Alias `mysql` to `mysql2` so we can maintain backwards compatibility
    if (options.client === 'mysql') {
        options.client = 'mysql2';
    }

    const client = options.client;

    if (client === 'sqlite3') {
        options.useNullAsDefault = options.useNullAsDefault || false;
    }

    if (client === 'mysql2') {
        options.connection.timezone = options.connection.timezone || 'Z';
        options.connection.charset = options.connection.charset || 'utf8mb4';
        options.connection.decimalNumbers = true;

        delete options.connection.filename;
    }

    return knex(options);
};

/**
 * If you instantiate knex, you won't know if the connection works.
 * This helper functions is used to test the connection. It's basically a "test query".
 *
 * @param connection
 * @returns {Bluebird<R> | Bluebird<any> | Promise<T>}
 */
exports.ensureConnectionWorks = (connection) => {
    return connection.raw('SELECT 1+1 as RESULT;')
        .catch((err) => {
            if (err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT' || err.code === 'EAI_AGAIN') {
                throw new errors.DatabaseError({
                    message: 'Invalid database host.',
                    help: 'Please double check your database config.',
                    err: err
                });
            }

            throw new errors.DatabaseError({
                message: err.message,
                help: 'Unknown database error',
                err: err
            });
        });
};

/**
 * @description Helper to create a transaction.
 * @param callback
 * @returns {*}
 */
module.exports.createTransaction = function (connection, callback) {
    return connection.transaction(callback);
};

/**
 * @description Helper to create the migration table.
 *
 * @TODO: https://github.com/TryGhost/knex-migrator/issues/118
 * @TODO: https://github.com/TryGhost/knex-migrator/issues/91
 * @returns {Bluebird<R> | Bluebird<any> | * | Promise<T>}
 */
exports.createMigrationsTable = function createMigrationsTable(connection) {
    return connection('migrations')
        .catch(function (err) {
            // CASE: table does not exist
            if (err.errno === 1 || err.errno === 1146) {
                debug('Creating table: migrations');

                return connection.schema.createTable('migrations', function (table) {
                    table.increments().primary();
                    table.string('name');
                    table.string('version');
                    table.string('currentVersion');
                });
            }

            throw err;
        });
};

/**
 * Knex-migrator has an inbuilt feature to create a database if it does not exist yet.
 *
 * @param dbConfig
 * @returns {*}
 */
exports.createDatabaseIfNotExist = function createDatabaseIfNotExist(dbConfig) {
    const name = dbConfig.connection.database,
        charset = dbConfig.connection.charset || 'utf8mb4',
        collation = dbConfig.connection.collation || 'utf8mb4_general_ci';

    // @NOTE: Skip, because sqlite3 is a file based database.
    if (DatabaseInfo.isSQLiteConfig(dbConfig)) {
        return Promise.resolve();
    } else if (!DatabaseInfo.isMySQLConfig(dbConfig)) {
        return Promise.reject(new errors.KnexMigrateError({
            message: 'Database is not supported.'
        }));
    }

    const connection = exports.connect({
        client: dbConfig.client,
        connection: omit(dbConfig.connection, ['database'])
    });

    debug('Create database', name);

    return exports.ensureConnectionWorks(connection)
        .then(function () {
            return connection.raw('CREATE DATABASE `' + name + '` CHARACTER SET ' + charset + ' COLLATE ' + collation + ';');
        })
        .catch(function (err) {
            // CASE: DB exists
            if (err.errno === 1007) {
                return Promise.resolve();
            }

            throw new errors.DatabaseError({
                message: err.message,
                err: err,
                code: 'DATABASE_CREATION_FAILED'
            });
        })
        .finally(function () {
            return new Promise(function (resolve, reject) {
                connection.destroy(function (err) {
                    if (err) {
                        return reject(err);
                    }

                    debug('Destroy connection');
                    resolve();
                });
            });
        });
};

/**
 * Drops a database. Is called when you call `knex-migrator reset`.
 *
 * @param options
 * @returns {*}
 */
exports.drop = function drop(options) {
    options = options || {};

    const connection = options.connection,
        dbConfig = options.dbConfig;

    if (DatabaseInfo.isMySQLConfig(dbConfig)) {
        debug('Drop database: ' + dbConfig.connection.database);

        return connection.raw('DROP DATABASE `' + dbConfig.connection.database + '`;')
            .catch(function (err) {
                // CASE: database does not exist, skip
                if (err.errno === 1049) {
                    return Promise.resolve();
                }

                return Promise.reject(new errors.KnexMigrateError({
                    err: err
                }));
            });
    } else if (DatabaseInfo.isSQLiteConfig(dbConfig)) {
        // @NOTE: sqlite3 does not support "DROP DATABASE". We have to drop each table instead.
        // @NOTE: We cannot just remove the sqlite3 file, because any database connection will get invalid.
        return connection.raw('SELECT name FROM sqlite_master WHERE type="table";')
            .then(function (tables) {
                return Promise.each(tables, function (table) {
                    if (table.name === 'sqlite_sequence') {
                        debug('Skip drop table: ' + table.name);
                        return Promise.resolve();
                    }

                    debug('Drop table: ' + table.name);
                    return connection.schema.dropTableIfExists(table.name);
                });
            })
            .catch(function (err) {
                // CASE: database file was never initialised
                if (err.errno === 10) {
                    return Promise.resolve();
                }

                return Promise.reject(new errors.KnexMigrateError({
                    err: err
                }));
            });
    } else {
        return Promise.reject(new errors.KnexMigrateError({
            message: 'Database client not supported: ' + dbConfig.client
        }));
    }
};
<commit_msg>Refactored migrations table creation to use helper<commit_after>const knex = require('knex'),
    Promise = require('bluebird'),
    omit = require('lodash/omit'),
    debug = require('debug')('knex-migrator:database'),
    errors = require('./errors');
const DatabaseInfo = require('@tryghost/database-info');

/**
 * @NOTE: Knex-migrator only supports knex query builder.
 *
 * @param options
 * @returns {Knex.QueryBuilder | Knex}
 */
exports.connect = function connect(options) {
    options = options || {};

    // Alias `mysql` to `mysql2` so we can maintain backwards compatibility
    if (options.client === 'mysql') {
        options.client = 'mysql2';
    }

    const client = options.client;

    if (client === 'sqlite3') {
        options.useNullAsDefault = options.useNullAsDefault || false;
    }

    if (client === 'mysql2') {
        options.connection.timezone = options.connection.timezone || 'Z';
        options.connection.charset = options.connection.charset || 'utf8mb4';
        options.connection.decimalNumbers = true;

        delete options.connection.filename;
    }

    return knex(options);
};

/**
 * If you instantiate knex, you won't know if the connection works.
 * This helper functions is used to test the connection. It's basically a "test query".
 *
 * @param connection
 * @returns {Bluebird<R> | Bluebird<any> | Promise<T>}
 */
exports.ensureConnectionWorks = (connection) => {
    return connection.raw('SELECT 1+1 as RESULT;')
        .catch((err) => {
            if (err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT' || err.code === 'EAI_AGAIN') {
                throw new errors.DatabaseError({
                    message: 'Invalid database host.',
                    help: 'Please double check your database config.',
                    err: err
                });
            }

            throw new errors.DatabaseError({
                message: err.message,
                help: 'Unknown database error',
                err: err
            });
        });
};

/**
 * @description Helper to create a transaction.
 * @param callback
 * @returns {*}
 */
module.exports.createTransaction = function (connection, callback) {
    return connection.transaction(callback);
};

/**
 * @description Helper to create the migration table.
 *
 * @TODO: https://github.com/TryGhost/knex-migrator/issues/118
 * @TODO: https://github.com/TryGhost/knex-migrator/issues/91
 * @returns {Bluebird<R> | Bluebird<any> | * | Promise<T>}
 */
exports.createMigrationsTable = async function createMigrationsTable(connection) {
    const hasTable = await connection.schema.hasTable('migrations');
    if (hasTable) {
        return;
    }

    // CASE: table does not exist
    debug('Creating table: migrations');

    await connection.schema.createTable('migrations', function (table) {
        table.increments().primary();
        table.string('name');
        table.string('version');
        table.string('currentVersion');
    });
};

/**
 * Knex-migrator has an inbuilt feature to create a database if it does not exist yet.
 *
 * @param dbConfig
 * @returns {*}
 */
exports.createDatabaseIfNotExist = function createDatabaseIfNotExist(dbConfig) {
    const name = dbConfig.connection.database,
        charset = dbConfig.connection.charset || 'utf8mb4',
        collation = dbConfig.connection.collation || 'utf8mb4_general_ci';

    // @NOTE: Skip, because sqlite3 is a file based database.
    if (DatabaseInfo.isSQLiteConfig(dbConfig)) {
        return Promise.resolve();
    } else if (!DatabaseInfo.isMySQLConfig(dbConfig)) {
        return Promise.reject(new errors.KnexMigrateError({
            message: 'Database is not supported.'
        }));
    }

    const connection = exports.connect({
        client: dbConfig.client,
        connection: omit(dbConfig.connection, ['database'])
    });

    debug('Create database', name);

    return exports.ensureConnectionWorks(connection)
        .then(function () {
            return connection.raw('CREATE DATABASE `' + name + '` CHARACTER SET ' + charset + ' COLLATE ' + collation + ';');
        })
        .catch(function (err) {
            // CASE: DB exists
            if (err.errno === 1007) {
                return Promise.resolve();
            }

            throw new errors.DatabaseError({
                message: err.message,
                err: err,
                code: 'DATABASE_CREATION_FAILED'
            });
        })
        .finally(function () {
            return new Promise(function (resolve, reject) {
                connection.destroy(function (err) {
                    if (err) {
                        return reject(err);
                    }

                    debug('Destroy connection');
                    resolve();
                });
            });
        });
};

/**
 * Drops a database. Is called when you call `knex-migrator reset`.
 *
 * @param options
 * @returns {*}
 */
exports.drop = function drop(options) {
    options = options || {};

    const connection = options.connection,
        dbConfig = options.dbConfig;

    if (DatabaseInfo.isMySQLConfig(dbConfig)) {
        debug('Drop database: ' + dbConfig.connection.database);

        return connection.raw('DROP DATABASE `' + dbConfig.connection.database + '`;')
            .catch(function (err) {
                // CASE: database does not exist, skip
                if (err.errno === 1049) {
                    return Promise.resolve();
                }

                return Promise.reject(new errors.KnexMigrateError({
                    err: err
                }));
            });
    } else if (DatabaseInfo.isSQLiteConfig(dbConfig)) {
        // @NOTE: sqlite3 does not support "DROP DATABASE". We have to drop each table instead.
        // @NOTE: We cannot just remove the sqlite3 file, because any database connection will get invalid.
        return connection.raw('SELECT name FROM sqlite_master WHERE type="table";')
            .then(function (tables) {
                return Promise.each(tables, function (table) {
                    if (table.name === 'sqlite_sequence') {
                        debug('Skip drop table: ' + table.name);
                        return Promise.resolve();
                    }

                    debug('Drop table: ' + table.name);
                    return connection.schema.dropTableIfExists(table.name);
                });
            })
            .catch(function (err) {
                // CASE: database file was never initialised
                if (err.errno === 10) {
                    return Promise.resolve();
                }

                return Promise.reject(new errors.KnexMigrateError({
                    err: err
                }));
            });
    } else {
        return Promise.reject(new errors.KnexMigrateError({
            message: 'Database client not supported: ' + dbConfig.client
        }));
    }
};
<|endoftext|>


<commit_before>import accountsModule from '../resources/accounts';
/**
 * 每次切换页面时判断是否需要登录，如果需要就跳转到登录页
 * @param {Object} $rootScope
 */
function LoginInterceptor($rootScope, $location, Accounts) {
  $rootScope.$on('$stateChangeStart', function(event, toState, toParams) {
    if (toState.data && toState.data.requiresLogin && !isLoggedIn()) {
      $rootScope.returnToStateName = toState.name;
      $rootScope.returnToStateParams = toParams;
      $location.path('/login');
    }
  });

  function isLoggedIn() {
    return !!Accounts.getCurrent();
  }
}

LoginInterceptor.$inject = ['$rootScope', '$location', 'Accounts'];

export default angular.module('security.stateInterceptor', [
  accountsModule.name
]).run(LoginInterceptor);
<commit_msg>Fix a bug that cause redirection to login page failed at the second time<commit_after>import accountsModule from '../resources/accounts';
/**
 * 每次切换页面时判断是否需要登录，如果需要就跳转到登录页
 * @param {Object} $rootScope
 */
function LoginInterceptor($rootScope, $location, Accounts) {
  $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
    if (toState.data && toState.data.requiresLogin && !isLoggedIn()) {
      event.preventDefault();

      $rootScope.fromStateName = fromState.name;
      $rootScope.fromStateParams = fromParams;
      $rootScope.returnToStateName = toState.name;
      $rootScope.returnToStateParams = toParams;

      // 直接跳转的话有一定概率不会起作用
      // https://github.com/angular/angular.js/issues/9607
      $rootScope.$evalAsync(function() {
        $location.path('/login');
      });
    }
  });

  function isLoggedIn() {
    return !!Accounts.getCurrent();
  }
}

LoginInterceptor.$inject = ['$rootScope', '$location', 'Accounts'];

export default angular.module('security.stateInterceptor', [
  accountsModule.name
]).run(LoginInterceptor);
<|endoftext|>