diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..e274c91 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,86 @@ +version: 2 + +jobs: + # Build & Deploy against development backend rer + "build-dev-deploy-test": + docker: + - image: docker:17.06.1-ce-git + steps: + # Initialization. + - checkout + - setup_remote_docker + - run: + name: Installation of build dependencies. + command: apk add --no-cache bash + + # Restoration of node_modules from cache. + - restore_cache: + key: docker-tc-email-service-{{ checksum "package-lock.json" }} + + # Build of Docker image. + - run: + name: Build of Docker image + command: ./build.sh DEV + + # Caching node modules. + - save_cache: + key: docker-tc-email-service-{{ checksum "package-lock.json" }} + paths: + - node_modules + + # Deployment. + - run: + name: Installing AWS client + command: | + apk add --no-cache jq py-pip sudo + sudo pip install awscli --upgrade + + - deploy: + command: ./deploy.sh DEV $CIRCLE_SHA1 + + "build-prod": + docker: + - image: docker:17.06.1-ce-git + steps: + # Initialization. + - checkout + - setup_remote_docker + - run: + name: Installation of build dependencies. + command: apk add --no-cache bash + + # Restoration of node_modules from cache. + - restore_cache: + key: docker-tc-email-service-{{ checksum "package-lock.json" }} + + # Build of Docker image. + - run: + name: Build of Docker image + command: ./build.sh PROD + + # Caching node modules. + - save_cache: + key: docker-tc-email-service-{{ checksum "package-lock.json" }} + paths: + - node_modules + + # Deployment. + - run: + name: Installing AWS client + command: | + apk add --no-cache jq py-pip sudo + sudo pip install awscli --upgrade + + - deploy: + command: ./deploy.sh PROD $CIRCLE_SHA1 + +workflows: + version: 2 + build: + jobs: + # Development builds are executed on "develop" branch only. + - "build-dev-deploy-test": + filters: + branches: + only: "dev" + diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..23b8d3f --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +"parserOptions": { + "ecmaVersion": 6 + } diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37d7e73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..81b7d32 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +# Builds production version of Community App inside Docker container, +# and runs it against the specified Topcoder backend (development or +# production) when container is executed. + +FROM node:8.2.1 +LABEL app="tc email" version="1.0" + +WORKDIR /opt/app +COPY . . +RUN npm install +RUN npm install dotenv --save +RUN npm run lint +CMD ["npm", "start"] diff --git a/README.md b/README.md index 2c09945..19f49f5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,101 @@ -# tc-email-service -General purpose email service +# TOPCODER EMAIL SERIES - EMAIL SERVER + + +## Dependencies +- nodejs https://nodejs.org/en/ (v8+) +- Heroku Toolbelt https://toolbelt.heroku.com +- git +- PostgreSQL 9.5 + + +## Configuration +Configuration for the notification server is at `config/default.js`. +The following parameters can be set in config files or in env variables: +- LOG_LEVEL: the log level +- PORT: the notification server port +- authSecret: TC auth secret +- authDomain: TC auth domain +- validIssuers: TC auth valid issuers +- jwksUri: TC auth JWKS URI +- DATABASE_URL: URI to PostgreSQL database +- DATABASE_OPTIONS: database connection options +- KAFKA_URL: comma separated Kafka hosts +- KAFKA_TOPIC_IGNORE_PREFIX: ignore this prefix for topics in the Kafka +- KAFKA_GROUP_ID: Kafka consumer group id +- KAFKA_CLIENT_CERT: Kafka connection certificate, optional; + if not provided, then SSL connection is not used, direct insecure connection is used; + if provided, it can be either path to certificate file or certificate content +- KAFKA_CLIENT_CERT_KEY: Kafka connection private key, optional; + if not provided, then SSL connection is not used, direct insecure connection is used; + if provided, it can be either path to private key file or private key content +- TEMPLATE_MAP: the map between topic and SendGrid template id + + +Configuration for the connect notification server is at `connect/config.js`. +The following parameters can be set in config files or in env variables: +- TEMPLATE_MAP: the map between topic and SendGrid template id +- SUBJECT_MAP: the map between topic and SendGrid email subject +- EMAIL_FROM: from email to use to send email with SendGrid +- SENDGRID_API_KEY: SendGrid API key + + +Note that the above two configuration are separate because the common notification server config +will be deployed to a NPM package, the connect notification server will use that NPM package, +the connection notification server should only use API exposed by the index.js. + +## TC API Admin Token + +An admin token is needed to access TC API. This is already configured Postman notification +server API environment TC_ADMIN_TOKEN variable. +In case it expires, you may get a new token in this way: + +- use Chrome to browse connect.topcoder-dev.com +- open developer tools, click the Network tab +- log in with suser1 / Topcoder123, or mess / appirio123 +- once logged in, open some project, for example https://connect.topcoder-dev.com/projects/1936 and in the network inspector + look for the call to the project api and get the token from the auth header, see + http://pokit.org/get/img/68cdd34f3d205d6d9bd8bddb07bdc216.jpg + + +## Local deployment +- for local development environment you can set variables as following: + - `authSecret`, `authDomain`, `validIssuers` can get from [tc-project-service config](https://github.com/topcoder-platform/tc-project-service/blob/dev/config/default.json) + - `PORT=4001` because **connect-app** call this port by default + - `jwksUri` - any + - `KAFKA_TOPIC_IGNORE_PREFIX=joan-26673.` (with point at the end) + - `KAFKA_URL`, `KAFKA_CLIENT_CERT` and `KAFKA_CLIENT_CERT_KEY` get from [tc-bus-api readme](https://github.com/topcoder-platform/tc-bus-api/tree/dev) +- start local PostgreSQL db, create an empty database, update the config/default.js DATABASE_URL param to point to the db +- install dependencies `npm i` +- run code lint check `npm run lint` +- start connect notification server `npm start` +- the app is running at `http://localhost:4001`, it also starts Kafka consumer to listen for events, send emails using SendGrid and save emails data to database + + +## Heroku deployment + +- git init +- git add . +- git commit -m 'message' +- heroku login +- heroku create [application-name] // choose a name, or leave it empty to use generated one +- heroku addons:create heroku-postgresql:hobby-dev +- note that you may need to wait for several minutes before the PostgreSQL database is ready +- optionally, to set some environment variables in heroku, run command like: + `heroku config:set KAFKA_CLIENT_CERT=path/to/certificate/file` + `heroku config:set KAFKA_CLIENT_CERT_KEY=path/to/private/key/file` + `heroku config:set KAFKA_GROUP_ID=some-group` + etc. +- git push heroku master // push code to Heroku + + +## Verification + +- start the app following above sections +- Import `docs/tc-email-server-api-local-env.postman_environment.json` and `docs/tc-email-server-api.postman_collection.json` to Postman +- in Postman, using the email server API collection and environment to run the tests + + +## Swagger + +Swagger API definition is provided at `docs/swagger_api.yaml`, +you may check it at `http://editor.swagger.io`. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..b275b6e --- /dev/null +++ b/build.sh @@ -0,0 +1,75 @@ +#!/bin/bash +set -eo pipefail + +# Builds Docker image of Community App application. +# This script expects a single argument: NODE_ENV, which must be either +# "development" or "production". + +NODE_ENV=$1 + +ENV=$1 +AWS_REGION=$(eval "echo \$${ENV}_AWS_REGION") +AWS_ACCESS_KEY_ID=$(eval "echo \$${ENV}_AWS_ACCESS_KEY_ID") +AWS_SECRET_ACCESS_KEY=$(eval "echo \$${ENV}_AWS_SECRET_ACCESS_KEY") +AWS_ACCOUNT_ID=$(eval "echo \$${ENV}_AWS_ACCOUNT_ID") +AWS_REPOSITORY=$(eval "echo \$${ENV}_AWS_REPOSITORY") + +#App variables + +AUTHDOMAIN=$(eval "echo \$${ENV}_AUTHDOMAIN") +AUTHSECRET=$(eval "echo \$${ENV}_AUTHSECRET") +VALIDISSUERS=$(eval "echo \$${ENV}_VALIDISSUERS") + +KAFKA_CLIENT_CERT=$(eval "echo \$${ENV}_KAFKA_CLIENT_CERT") +KAFKA_CLIENT_CERT_KEY=$(eval "echo \$${ENV}_KAFKA_CLIENT_CERT_KEY") +KAFKA_URL=$(eval "echo \$${ENV}_KAFKA_URL") +SENDGRID_API_KEY=$(eval "echo \$${ENV}_SENDGRID_API_KEY") + + +DB_DATABASE=$(eval "echo \$${ENV}_DB_DATABASE") +DB_HOST=$(eval "echo \$${ENV}_DB_HOST") +DB_PASSWORD=$(eval "echo \$${ENV}_DB_PASSWORD") +DB_PORT=$(eval "echo \$${ENV}_DB_PORT") +DB_USER=$(eval "echo \$${ENV}_DB_USER") +DATABASE_URL=postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_DATABASE; + + +KAFKA_GROUP_ID=$(eval "echo \$${ENV}_KAFKA_GROUP_ID") +EMAIL_FROM=$(eval "echo \$${ENV}_EMAIL_FROM") +LOG_LEVEL=$(eval "echo \$${ENV}_LOG_LEVEL") +NODE_ENV=$(eval "echo \$${ENV}_NODE_ENV") +NODE_PORT=$(eval "echo \$${ENV}_NODE_PORT") +JWKSURI=$(eval "echo \$${ENV}_JWKSURI") +TEMPLATE_MAP=$(eval "echo \$${ENV}_TEMPLATE_MAP") + + +TAG=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/tc-email-service:$CIRCLE_SHA1 + +docker build -t $TAG . + +# Copies "node_modules" from the created image, if necessary for caching. +docker create --name app $TAG + +if [ -d node_modules ] +then + # If "node_modules" directory already exists, we should compare + # "package-lock.json" from the code and from the container to decide, + # whether we need to re-cache, and thus to copy "node_modules" from + # the Docker container. + mv package-lock.json old-package-lock.json + docker cp app:/opt/app/package-lock.json package-lock.json + # docker cp .env app:/opt/app/ + set +eo pipefail + UPDATE_CACHE=$(cmp package-lock.json old-package-lock.json) + set -eo pipefail +else + # If "node_modules" does not exist, then cache must be created. + UPDATE_CACHE=1 +fi + +if [ "$UPDATE_CACHE" == 1 ] +then + docker cp app:/opt/app/node_modules . +fi + + diff --git a/config/default.js b/config/default.js new file mode 100644 index 0000000..155638b --- /dev/null +++ b/config/default.js @@ -0,0 +1,43 @@ +/** + * The configuration file. + */ +module.exports = { + LOG_LEVEL: process.env.LOG_LEVEL, + PORT: process.env.PORT, + authSecret: process.env.authSecret, + authDomain: process.env.authDomain, + jwksUri: process.env.jwksUri, + DATABASE_URL: process.env.DATABASE_URL, + DATABASE_OPTIONS: { + dialect: 'postgres', + dialectOptions: { + ssl: process.env.DATABASE_SSL != null, + }, + pool: { + max: 5, + min: 0, + idle: 10000, + }, + }, + DISABLE_LOGGING: process.env.DISABLE_LOGGING || 'false', + + validIssuers: process.env.validIssuers ? process.env.validIssuers.replace(/\\"/g, '') : null, + KAFKA_URL: process.env.KAFKA_URL, + KAFKA_TOPIC_IGNORE_PREFIX: process.env.KAFKA_TOPIC_IGNORE_PREFIX, + KAFKA_GROUP_ID: process.env.KAFKA_GROUP_ID, + KAFKA_CLIENT_CERT: process.env.KAFKA_CLIENT_CERT ? process.env.KAFKA_CLIENT_CERT.replace('\\n', '\n') : null, + KAFKA_CLIENT_CERT_KEY: process.env.KAFKA_CLIENT_CERT_KEY ? + process.env.KAFKA_CLIENT_CERT_KEY.replace('\\n', '\n') : null, + + // mapping from event type to sendgrid email template id + TEMPLATE_MAP: process.env.TEMPLATE_MAP, + SENDGRID_API_KEY: process.env.SENDGRID_API_KEY || '', + EMAIL_FROM: process.env.EMAIL_FROM || 'no-reply@topcoder.com', + + // temporary not in use feature + EMAIL_MAX_ERRORS: process.env.EMAIL_MAX_ERRORS || 2, + EMAIL_PAUSE_TIME: process.env.EMAIL_PAUSE_TIME || 30, + + //in every 2 minutes will retry for failed status + EMAIL_RETRY_SCHEDULE: process.env.EMAIL_RETRY_SCHEDULE || '0 */2 * * * *', +}; diff --git a/config/development.js b/config/development.js new file mode 100644 index 0000000..af6397c --- /dev/null +++ b/config/development.js @@ -0,0 +1,7 @@ +/** + * The development configuration file. These config will override default config. + */ +module.exports = { + LOG_LEVEL: process.env.LOG_LEVEL || 'debug', + PORT: process.env.PORT || 4000, +}; diff --git a/config/production.js b/config/production.js new file mode 100644 index 0000000..162d717 --- /dev/null +++ b/config/production.js @@ -0,0 +1,8 @@ +/** + * The production configuration file. These config will override default config. + */ +module.exports = { + LOG_LEVEL: process.env.LOG_LEVEL || 'error', + PORT: process.env.PORT || 4000, + DISABLE_LOGGING: process.env.DISABLE_LOGGING || 'true', +}; diff --git a/config/test.js b/config/test.js new file mode 100644 index 0000000..32f39d9 --- /dev/null +++ b/config/test.js @@ -0,0 +1,7 @@ +/** + * The test configuration file. These config will override default config. + */ +module.exports = { + LOG_LEVEL: process.env.LOG_LEVEL || 'debug', + PORT: process.env.PORT || 4000, +}; diff --git a/connect/connectEmailServer.js b/connect/connectEmailServer.js new file mode 100644 index 0000000..30bb293 --- /dev/null +++ b/connect/connectEmailServer.js @@ -0,0 +1,49 @@ +/** + * This is TopCoder connect email server. + */ +'use strict'; + +global.Promise = require('bluebird'); + +const _ = require('lodash'); +const config = require('config'); +const emailServer = require('../index'); +const service = require('./service'); + +// set configuration for the server, see ../config/default.js for available config parameters +// setConfig should be called before initDatabase and start functions +emailServer.setConfig({ LOG_LEVEL: 'debug' }); + +// add topic handlers, +// handler is used build a notification list for a message of a topic, +// it is defined as: function(topic, message, callback), +// the topic is topic name, +// the message is JSON event message, +// the callback is function(error, templateId), where templateId is the used SendGrid template id +const handler = (topic, message, callback) => { + const templateId = config.TEMPLATE_MAP[topic]; + if (templateId === undefined) { + return callback(null, { success: false, error: `Template not found for topic ${topic}` }); + } + + const replyTo = message.replyTo ? message.replyTo : config.EMAIL_FROM; + service.sendEmail(templateId, message.recipients, message.data, replyTo).then(() => { + callback(null, { success: true }); + }).catch((err) => { + callback(null, { success: false, error: err }); + }); +}; + +// init all events +_.keys(config.TEMPLATE_MAP).forEach((eventType) => { + emailServer.addTopicHandler(eventType, handler); +}); + +// init database, it will clear and re-create all tables +emailServer + .initDatabase() + .then(() => emailServer.start()) + .catch((e) => console.log(e)); // eslint-disable-line no-console + +// if no need to init database, then directly start the server: +// emailServer.start(); diff --git a/connect/service.js b/connect/service.js new file mode 100644 index 0000000..10c860b --- /dev/null +++ b/connect/service.js @@ -0,0 +1,24 @@ +/** + * This is TopCoder connect email service. + */ +'use strict'; + +const sgMail = require('@sendgrid/mail'); +const config = require('config'); + +// set api key for SendGrid email client +sgMail.setApiKey(config.SENDGRID_API_KEY); + +const sendEmail = (templateId, to, substitutions, replyTo) => // send email + sgMail.send({ + to, + templateId, + substitutions, + from: config.EMAIL_FROM, + substitutionWrappers: ['{{', '}}'], + replyTo, + }); + +module.exports = { + sendEmail, +} diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..ee48d4d --- /dev/null +++ b/deploy.sh @@ -0,0 +1,249 @@ +#!/bin/bash +set -eo pipefail + +# more bash-friendly output for jq +JQ="jq --raw-output --exit-status" + +ENV=$1 +TAG=$2 +PROVIDER=$3 +COUNTER_LIMIT=20 +# Counter limit will be caluculaed based on sleep seconds + +if [[ -z "$ENV" ]] ; then + echo "Environment should be set on startup with one of the below values" + echo "ENV must be one of - DEV, QA, PROD or LOCAL" + exit +fi +if [[ -z "$TAG" ]] ; then + echo "TAG must be specificed for image" + exit +fi +if [[ -z "$PROVIDER" ]] ; then + PROVIDER=$ENV +fi + +AWS_REGION=$(eval "echo \$${ENV}_AWS_REGION") +AWS_ACCESS_KEY_ID=$(eval "echo \$${ENV}_AWS_ACCESS_KEY_ID") +AWS_SECRET_ACCESS_KEY=$(eval "echo \$${ENV}_AWS_SECRET_ACCESS_KEY") +AWS_ACCOUNT_ID=$(eval "echo \$${ENV}_AWS_ACCOUNT_ID") +AWS_REPOSITORY=$(eval "echo \$${ENV}_AWS_REPOSITORY") +AWS_ECS_CLUSTER=$(eval "echo \$${ENV}_AWS_ECS_CLUSTER") +AWS_ECS_SERVICE=$(eval "echo \$${ENV}_AWS_ECS_SERVICE") +family=$(eval "echo \$${ENV}_AWS_ECS_TASK_FAMILY") +AWS_ECS_CONTAINER_NAME=$(eval "echo \$${ENV}_AWS_ECS_CONTAINER_NAME") + + +AUTH_DOMAIN=$(eval "echo \$${ENV}_AUTHDOMAIN") +AUTH_SECRET=$(eval "echo \$${ENV}_AUTHSECRET") +VALID_ISSUERS=$(eval "echo \$${ENV}_VALIDISSUERS") + +KAFKA_CLIENT_CERT=$(eval "echo \$${ENV}_KAFKA_CLIENT_CERT") +KAFKA_CLIENT_CERT_KEY=$(eval "echo \$${ENV}_KAFKA_CLIENT_CERT_KEY") +KAFKA_URL=$(eval "echo \$${ENV}_KAFKA_URL") +SENDGRID_API_KEY=$(eval "echo \$${ENV}_SENDGRID_API_KEY") + + +DB_DATABASE=$(eval "echo \$${ENV}_DB_DATABASE") +DB_HOST=$(eval "echo \$${ENV}_DB_HOST") +DB_PASSWORD=$(eval "echo \$${ENV}_DB_PASSWORD") +DB_PORT=$(eval "echo \$${ENV}_DB_PORT") +DB_USER=$(eval "echo \$${ENV}_DB_USER") +DATABASE_URL=postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_DATABASE; + +# following can have in config file + +KAFKA_GROUP_ID=$(eval "echo \$${ENV}_KAFKA_GROUP_ID") +EMAIL_FROM=$(eval "echo \$${ENV}_EMAIL_FROM") +LOG_LEVEL=$(eval "echo \$${ENV}_LOG_LEVEL") +NODE_ENV=$(eval "echo \$${ENV}_NODE_ENV") +PORT=$(eval "echo \$${ENV}_NODE_PORT") +JWKS_URI=$(eval "echo \$${ENV}_JWKSURI") +TEMPLATE_MAP=$(eval "echo \$${ENV}_TEMPLATE_MAP") + +EMAIL_MAX_ERRORS=$(eval "echo \$${ENV}_EMAIL_MAX_ERRORS") +EMAIL_PAUSE_TIME=$(eval "echo \$${ENV}_EMAIL_PAUSE_TIME") +EMAIL_RETRY_SCHEDULE=$(eval "echo \"\$${ENV}_EMAIL_RETRY_SCHEDULE\"") +DISABLE_LOGGING=$(eval "echo \$${ENV}_DISABLE_LOGGING") + +configure_aws_cli() { + aws --version + aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID + aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY + aws configure set default.region $AWS_REGION + aws configure set default.output json + echo "Configured AWS CLI." +} + +push_ecr_image() { + echo "Pushing Docker Image...." + eval $(aws ecr get-login --region $AWS_REGION --no-include-email) + docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$AWS_REPOSITORY:$TAG + echo "Docker Image published." + +} + +deploy_cluster() { + + make_task_def + register_definition + update_result=$(aws ecs update-service --cluster $AWS_ECS_CLUSTER --service $AWS_ECS_SERVICE --task-definition $revision ) + result=$(echo $update_result | $JQ '.service.taskDefinition' ) + echo $result + if [[ $result != $revision ]]; then + echo "Error updating service." + return 1 + fi + + echo "Update service intialised successfully for deployment" + return 0 +} + +make_task_def(){ + task_template='{ + "family": "%s", + "requiresCompatibilities": ["EC2", "FARGATE"], + "networkMode": "awsvpc", + "executionRoleArn": "arn:aws:iam::%s:role/ecsTaskExecutionRole", + "cpu": "1024", + "memory": "2048", + "containerDefinitions": [{ + "name": "%s", + "memory": 1000, + "cpu" : 0, + "image": "%s.dkr.ecr.%s.amazonaws.com/%s:%s", + "environment": [ + { + "name" : "ENV", + "value" : "%s" + }, + { + "name": "authDomain", + "value": "%s" + }, + { + "name": "authSecret", + "value": "%s" + }, + { + "name": "DATABASE_URL", + "value": "%s" + }, + { + "name": "EMAIL_FROM", + "value": "%s" + }, + { + "name": "jwksUri", + "value": "%s" + }, + { + "name": "KAFKA_CLIENT_CERT", + "value": "%s" + }, + { + "name": "KAFKA_CLIENT_CERT_KEY", + "value": "%s" + }, + { + "name": "KAFKA_GROUP_ID", + "value": "%s" + }, + { + "name": "KAFKA_URL", + "value": "%s" + }, + { + "name": "LOG_LEVEL", + "value": "%s" + }, + { + "name": "PORT", + "value": "%s" + }, + { + "name": "SENDGRID_API_KEY", + "value": "%s" + }, + { + "name": "TEMPLATE_MAP", + "value": "%s" + }, + { + "name": "validIssuers", + "value": "%s" + }, + { + "name": "EMAIL_MAX_ERRORS", + "value": "%s" + }, + { + "name": "EMAIL_PAUSE_TIME", + "value": "%s" + }, + { + "name": "EMAIL_RETRY_SCHEDULE", + "value": "%s" + }, + { + "name": "DISABLE_LOGGING", + "value": "%s" + } + ], + "portMappings": [ + { + "hostPort": %s, + "protocol": "tcp", + "containerPort": %s + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/aws/ecs/%s", + "awslogs-region": "%s", + "awslogs-stream-prefix": "%s" + } + } + } + ] + }' + + task_def=$(printf "$task_template" $family $AWS_ACCOUNT_ID $AWS_ECS_CONTAINER_NAME $AWS_ACCOUNT_ID $AWS_REGION $AWS_REPOSITORY $TAG $ENV $AUTH_DOMAIN $AUTH_SECRET $DATABASE_URL $EMAIL_FROM "$JWKS_URI" "$KAFKA_CLIENT_CERT" "$KAFKA_CLIENT_CERT_KEY" $KAFKA_GROUP_ID $KAFKA_URL $LOG_LEVEL $PORT $SENDGRID_API_KEY "$TEMPLATE_MAP" "$VALID_ISSUERS" $EMAIL_MAX_ERRORS $EMAIL_PAUSE_TIME "$EMAIL_RETRY_SCHEDULE" "$DISABLE_LOGGING" $PORT $PORT $AWS_ECS_CLUSTER $AWS_REGION $ENV) + +} + +register_definition() { + if revision=$(aws ecs register-task-definition --cli-input-json "$task_def" | $JQ '.taskDefinition.taskDefinitionArn'); then + echo "Revision: $revision" + else + echo "Failed to register task definition" + return 1 + fi + +} + +check_service_status() { + counter=0 + sleep 60 + servicestatus=`aws ecs describe-services --service $AWS_ECS_SERVICE --cluster $AWS_ECS_CLUSTER | $JQ '.services[].events[0].message'` + while [[ $servicestatus != *"steady state"* ]] + do + echo "Current event message : $servicestatus" + echo "Waiting for 30 sec to check the service status...." + sleep 30 + servicestatus=`aws ecs describe-services --service $AWS_ECS_SERVICE --cluster $AWS_ECS_CLUSTER | $JQ '.services[].events[0].message'` + counter=`expr $counter + 1` + if [[ $counter -gt $COUNTER_LIMIT ]] ; then + echo "Service does not reach steady state with in 10 minutes. Please check" + exit 1 + fi + done + echo "$servicestatus" +} + +configure_aws_cli +push_ecr_image +deploy_cluster +check_service_status + diff --git a/docs/swagger_api.yaml b/docs/swagger_api.yaml new file mode 100644 index 0000000..4838664 --- /dev/null +++ b/docs/swagger_api.yaml @@ -0,0 +1,74 @@ +swagger: "2.0" +info: + title: "TOPCODER EMAIL SERIES - EMAIL SERVER" + description: "TOPCODER EMAIL SERIES - EMAIL SERVER" + version: "1.0.0" +host: "localhost:4001" +schemes: +- "http" +securityDefinitions: + jwt: + type: apiKey + name: Authorization + in: header + description: JWT Authentication. Provide API Key in the form 'Bearer <token>'. + +paths: + /templates/eventType/{name}: + get: + description: + get email template placholders name + produces: + - application/json + security: + - jwt: [] + parameters: + - name: name + in: path + description: The Kafka topic name + required: true + type: string + responses: + 200: + description: OK + schema: + type: object + properties: + items: + type: array + items: + type: string + 400: + description: "Bad request error." + schema: + $ref: "#/definitions/Error" + 404: + description: "Template is not found error." + schema: + $ref: "#/definitions/Error" + 401: + description: "Authentication failed." + schema: + $ref: "#/definitions/Error" + 500: + description: "Internal server error." + schema: + $ref: "#/definitions/Error" +definitions: + Error: + properties: + error: + type: string + details: + type: array + items: + type: object + properties: + message: + type: string + path: + type: string + type: + type: string + context: + type: object diff --git a/docs/tc-email-server-api-local-env.postman_environment.json b/docs/tc-email-server-api-local-env.postman_environment.json new file mode 100644 index 0000000..769d5ac --- /dev/null +++ b/docs/tc-email-server-api-local-env.postman_environment.json @@ -0,0 +1,28 @@ +{ + "id": "31be4325-8bb7-7c8b-3a41-95efc8859219", + "name": "tc-email-server-api-local-env", + "values": [ + { + "enabled": true, + "key": "URL", + "value": "http://localhost:4001/templates/eventType", + "type": "text" + }, + { + "enabled": true, + "key": "TOKEN", + "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjMwNTM4NCwiaXNzIjoiaHR0cHM6Ly9hcGkudG9wY29kZXItZGV2LmNvbSIsImlhdCI6MTUxMDYxODE0NiwiZXhwIjoxNTEzMjEwMTQ2fQ.Miqo8OpHIPU5kI_YwiOcVgIjTqFlLEvpOptjNzVnF8E", + "type": "text" + }, + { + "enabled": true, + "key": "TC_ADMIN_TOKEN", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJjb3BpbG90Il0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJtZXNzIiwiZXhwIjoxNTE3MDA3ODQ4LCJ1c2VySWQiOiIzMDUzODQiLCJpYXQiOjE1MTcwMDcyNDgsImVtYWlsIjoibWVzc0BhcHBpcmlvLmNvbSIsImp0aSI6Ijk1MTNmYjgyLTgzMDYtNDc4Yi1iMGIxLWMyYjA3ZWRmY2Y2MyJ9.PgT4jHG7WcPKaKb-U0FghcvLm0El-ssUc68KDEVQwvo", + "type": "text" + } + ], + "timestamp": 1517224825208, + "_postman_variable_scope": "environment", + "_postman_exported_at": "2018-01-29T11:23:03.406Z", + "_postman_exported_using": "Postman/5.5.2" +} \ No newline at end of file diff --git a/docs/tc-email-server-api.postman_collection.json b/docs/tc-email-server-api.postman_collection.json new file mode 100644 index 0000000..2ac52a3 --- /dev/null +++ b/docs/tc-email-server-api.postman_collection.json @@ -0,0 +1,274 @@ +{ + "info": { + "name": "tc-email-server-api", + "_postman_id": "f9d855bc-e0a5-3501-ab56-9a144830c431", + "description": "", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "failure", + "description": "", + "item": [ + { + "name": "Email - invalid token", + "event": [ + { + "listen": "test", + "script": { + "id": "6bbbb6e1-3ef8-4e4e-802e-c6bfd892378c", + "type": "text/javascript", + "exec": [ + "pm.test(\"Invalid GET request\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([403]);", + "});" + ] + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "BAD_TOKEN", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{URL}}/email.project.created", + "host": [ + "{{URL}}" + ], + "path": [ + "email.project.created" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "Email - invalid topic", + "event": [ + { + "listen": "test", + "script": { + "id": "5a75ca22-fb44-4a40-b6fa-d47f7421465b", + "type": "text/javascript", + "exec": [ + "pm.test(\"Bad request GET request\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([400]);", + "});" + ] + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{TC_ADMIN_TOKEN}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{URL}}/email.invalid", + "host": [ + "{{URL}}" + ], + "path": [ + "email.invalid" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "Email - invalid template", + "event": [ + { + "listen": "test", + "script": { + "id": "2e5a6ff8-bd38-457a-8c94-5cb745e11de5", + "type": "text/javascript", + "exec": [ + "pm.test(\"Not found GET request\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([404]);", + "});" + ] + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{TC_ADMIN_TOKEN}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{URL}}/email.tempate.invalid", + "host": [ + "{{URL}}" + ], + "path": [ + "email.tempate.invalid" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "Email - invalid template version", + "event": [ + { + "listen": "test", + "script": { + "id": "2e5a6ff8-bd38-457a-8c94-5cb745e11de5", + "type": "text/javascript", + "exec": [ + "pm.test(\"Not found GET request\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([404]);", + "});" + ] + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{TC_ADMIN_TOKEN}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{URL}}/email.template.no.version", + "host": [ + "{{URL}}" + ], + "path": [ + "email.template.no.version" + ] + }, + "description": "" + }, + "response": [] + } + ] + }, + { + "name": "Email - project.created", + "event": [ + { + "listen": "test", + "script": { + "id": "db43bdd5-1b84-42c3-9033-59a8756ab3ba", + "type": "text/javascript", + "exec": [ + "pm.test(\"Successful GET request\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([200,204]);", + "});" + ] + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{TC_ADMIN_TOKEN}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{URL}}/email.project.created", + "host": [ + "{{URL}}" + ], + "path": [ + "email.project.created" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "Email - project.updated", + "event": [ + { + "listen": "test", + "script": { + "id": "9732b9ef-c3b0-4ea3-92e3-624f9e84c6e6", + "type": "text/javascript", + "exec": [ + "pm.test(\"Successful GET request\", function () {", + " pm.expect(pm.response.code).to.be.oneOf([200,204]);", + "});" + ] + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{TC_ADMIN_TOKEN}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{URL}}/email.project.updated", + "host": [ + "{{URL}}" + ], + "path": [ + "email.project.updated" + ] + }, + "description": "" + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..cbb8441 --- /dev/null +++ b/index.js @@ -0,0 +1,104 @@ +/** + * This is entry point of the TopCoder notification server module. + */ +'use strict'; + +const config = require('config'); +const _ = require('lodash'); +const schedule = require('node-schedule'); +const logger = require('./src/common/logger'); +const errors = require('./src/common/errors'); + +config.TEMPLATE_MAP = JSON.parse(config.TEMPLATE_MAP); + +// key is topic name, e.g. 'notifications.connect.project.created'; +// value is handler for the topic to find user ids that should receive notifications for a message, +// it is defined as: function(topic, message, callback), +// the topic is topic name, +// the message is JSON event message, +// the callback is function(error, userIds), where userIds is an array of user ids to receive notifications +const handlers = {}; + +/** + * Set configuration, the default config will be overridden by the given config, + * unspecified config parameters will not be changed, i.e. still using default values. + * + * Note that setConfig should be called before the initDatabase and start functions. + * + * @param {Object} cfg the configuration to set + */ +function setConfig(cfg) { + if (!cfg) { + throw new errors.ValidationError('Missing configuration.'); + } + _.extend(config, cfg); +} + +/** + * Add topic handler for topic, override existing one if any. + * @param {String} topic the topic name + * @param {Function} handler the handler + */ +function addTopicHandler(topic, handler) { + if (!topic) { + throw new errors.ValidationError('Missing topic.'); + } + if (!handler) { + throw new errors.ValidationError('Missing handler.'); + } + handlers[topic] = handler; +} + +/** + * Remove topic handler for topic. + * @param {String} topic the topic name + */ +function removeTopicHandler(topic) { + if (!topic) { + throw new errors.ValidationError('Missing topic.'); + } + delete handlers[topic]; +} + +/** + * Get all topic handlers. + * @returns {Object} all topic handlers, key is topic name, value is handler + */ +function getAllHandlers() { + return handlers; +} + +/** + * Start the notification server. + */ +function start() { + if (_.isEmpty(handlers)) { + throw new errors.ValidationError('Missing handler(s).'); + } + // load app only after config is set + const app = require('./src/app'); + schedule.scheduleJob(config.EMAIL_RETRY_SCHEDULE, function() { + app.retryEmail(handlers).catch((err) => logger.error(err)); + }); + return app.start(handlers).catch((err) => logger.error(err)); +} + +/** + * Initialize database. All tables are cleared and re-created. + * @returns {Promise} promise to init db + */ +function initDatabase() { + // load models only after config is set + const models = require('./src/models'); + return models.init(true); +} + +// Exports +module.exports = { + setConfig, + addTopicHandler, + removeTopicHandler, + getAllHandlers, + start, + initDatabase, +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0c46d21 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3089 @@ +{ + "name": "tc-email", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@sendgrid/client": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-6.2.1.tgz", + "integrity": "sha512-FLqoh2UqmFs5R/92xzF1jYMLGU89rTgLK6XX+VA02YcfQW8rGjbMrj7zsSCQ7SLkeiWekmUU2+naeIO9L4dqxA==", + "requires": { + "@sendgrid/helpers": "6.2.1", + "@types/request": "2.0.13", + "request": "2.83.0" + } + }, + "@sendgrid/helpers": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-6.2.1.tgz", + "integrity": "sha512-WnQ4TV51Xln/X70lk6J1/3tfRHW3K4zagz19vlJrtQUtX1wwghOj926OqcMm5nOiBHEh+la3cvdzHENb09FsIA==", + "requires": { + "chalk": "2.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "@sendgrid/mail": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-6.2.1.tgz", + "integrity": "sha512-gTd8gMp4JVLGZhXb/DkyrjByTfIR1OvtpPpQLwO11Vz72x3JdPl4tJTtWA/svAFfN5UXnZtAomAvjJCdcd4lzw==", + "requires": { + "@sendgrid/client": "6.2.1", + "@sendgrid/helpers": "6.2.1" + } + }, + "@types/bluebird": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-JjNHCk6r6aR82aRf2yDtX5NAe8o=" + }, + "@types/body-parser": { + "version": "1.16.8", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.16.8.tgz", + "integrity": "sha512-BdN2PXxOFnTXFcyONPW6t0fHjz2fvRZHVMFpaS0wYr+Y8fWEaNOs4V8LEu/fpzQlMx+ahdndgTaGTwPC+J/EeA==", + "requires": { + "@types/express": "4.11.0", + "@types/node": "9.4.0" + } + }, + "@types/events": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.1.0.tgz", + "integrity": "sha512-y3bR98mzYOo0pAZuiLari+cQyiKk3UXRuT45h1RjhfeCzqkjaVsfZJNaxdgtk7/3tzOm1ozLTqEqMP3VbI48jw==" + }, + "@types/express": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.11.0.tgz", + "integrity": "sha512-N1Wdp3v4KmdO3W/CM7KXrDwM4xcVZjlHF2dAOs7sNrTUX8PY3G4n9NkaHlfjGFEfgFeHmRRjywoBd4VkujDs9w==", + "requires": { + "@types/body-parser": "1.16.8", + "@types/express-serve-static-core": "4.11.1", + "@types/serve-static": "1.13.1" + } + }, + "@types/express-jwt": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.34.tgz", + "integrity": "sha1-/b7kxq9cCiRu8qkz9VGZc8dxfwI=", + "requires": { + "@types/express": "4.11.0", + "@types/express-unless": "0.0.32" + } + }, + "@types/express-serve-static-core": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.11.1.tgz", + "integrity": "sha512-EehCl3tpuqiM8RUb+0255M8PhhSwTtLfmO7zBBdv0ay/VTd/zmrqDfQdZFsa5z/PVMbH2yCMZPXsnrImpATyIw==", + "requires": { + "@types/events": "1.1.0", + "@types/node": "9.4.0" + } + }, + "@types/express-unless": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.0.32.tgz", + "integrity": "sha512-6YpJyFNlDDnPnRjMOvJCoDYlSDDmG/OEEUsPk7yhNkL4G9hUYtgab6vi1CcWsGSSSM0CsvNlWTG+ywAGnvF03g==", + "requires": { + "@types/express": "4.11.0" + } + }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "requires": { + "@types/node": "9.4.0" + } + }, + "@types/geojson": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", + "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" + }, + "@types/lodash": { + "version": "4.14.97", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.97.tgz", + "integrity": "sha512-uIKSroywPzhFYw6azoXK2ChfgW/lTfeiNDJKSGOp5RQSAvMdKMI1g0C1NqdU3I9k9bb8iwcWXGwA0n+JSn/N3A==" + }, + "@types/mime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", + "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==" + }, + "@types/node": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.0.tgz", + "integrity": "sha512-zkYho6/4wZyX6o9UQ8rd0ReEaiEYNNCqYFIAACe2Tf9DrYlgzWW27OigYHnnztnnZQwVRpwWmZKegFmDpinIsA==" + }, + "@types/request": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.0.13.tgz", + "integrity": "sha512-AHX0Qe6+C6AdEG1YII35eL88L6dqwUzsZowOuewxz71qOY4L7fJpy2BPHijdou2ZEC3DSALA65FL0k7dj1u+3g==", + "requires": { + "@types/form-data": "2.2.1", + "@types/node": "9.4.0", + "@types/tough-cookie": "2.3.2" + } + }, + "@types/serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-jDMH+3BQPtvqZVIcsH700Dfi8Q3MIcEx16g/VdxjoqiGR/NntekB10xdBpirMKnPe9z2C5cBmL0vte0YttOr3Q==", + "requires": { + "@types/express-serve-static-core": "4.11.1", + "@types/mime": "2.0.0" + } + }, + "@types/tough-cookie": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha512-vOVmaruQG5EatOU/jM6yU2uCp3Lz6mK1P5Ztu4iJjfM4SVHU9XYktPUQtKlIXuahqXHdEyUarMrBEwg5Cwu+bA==" + }, + "accepts": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "requires": { + "mime-types": "2.1.17", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "axios": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.12.0.tgz", + "integrity": "sha1-uQewIhzDTsHJ+sGOx/B935V4W6Q=", + "requires": { + "follow-redirects": "0.0.7" + } + }, + "babel-runtime": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz", + "integrity": "sha1-eIuUtvY04luRvWxd9y1GdFevsAA=", + "requires": { + "core-js": "2.5.3" + } + }, + "backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", + "requires": { + "precond": "0.2.3" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", + "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "bin-protocol": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bin-protocol/-/bin-protocol-3.0.4.tgz", + "integrity": "sha1-RlqdNQb+sOEmtStbIWDZNuFbJ/Q=", + "requires": { + "lodash": "4.17.4", + "long": "3.2.0", + "protocol-buffers-schema": "3.3.2" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "1.6.15" + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.0" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-writer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", + "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=" + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "bunyan": { + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", + "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", + "requires": { + "dtrace-provider": "0.8.6", + "moment": "2.20.1", + "mv": "2.1.1", + "safe-json-stringify": "1.0.4" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "1.1.0", + "check-error": "1.0.2", + "deep-eql": "3.0.1", + "get-func-name": "2.0.0", + "pathval": "1.1.0", + "type-detect": "4.0.8" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cls-bluebird": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "requires": { + "is-bluebird": "1.0.2", + "shimmer": "1.2.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "codependency": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/codependency/-/codependency-0.1.4.tgz", + "integrity": "sha1-0XY6tyZL1wyR2WJumIYtN5K/jUo=", + "requires": { + "semver": "5.0.1" + }, + "dependencies": { + "semver": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.1.tgz", + "integrity": "sha1-n7P0AE+QDYPEeWj+QvdYPgWDLMk=" + } + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } + }, + "config": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/config/-/config-1.29.2.tgz", + "integrity": "sha1-Lr3JJjnrnQb//TAvHuMuKtDpThE=", + "requires": { + "json5": "0.4.0", + "os-homedir": "1.0.2" + } + }, + "connection-parse": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/connection-parse/-/connection-parse-0.0.7.tgz", + "integrity": "sha1-GOcxiqsGppkmc3KxDFIm0locmmk=" + }, + "console.history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/console.history/-/console.history-1.5.0.tgz", + "integrity": "sha1-36N/1g3DSnqqzxRotUfhiGEE2sk=" + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" + }, + "core-js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "requires": { + "object-assign": "4.1.1", + "vary": "1.1.2" + } + }, + "cron-parser": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.4.4.tgz", + "integrity": "sha512-lNWu5pGRGF7y4kl/uRXY69mC8n0qhjTIDQmc3MIfNY5eEvGyYqFPewn+2YQXybJoa2LVVOmDQ/1WTWyQzAM8uA==", + "requires": { + "is-nan": "1.2.1", + "moment-timezone": "0.5.14" + } + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "0.10.38" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "requires": { + "foreach": "2.0.5", + "object-keys": "1.0.11" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.4.5" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "dottie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.0.tgz", + "integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA=" + }, + "dtrace-provider": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.6.tgz", + "integrity": "sha1-QooiOv4DQl0s1tY0f99AxmkDVj0=", + "optional": true, + "requires": { + "nan": "2.8.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", + "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", + "requires": { + "base64url": "2.0.0", + "safe-buffer": "5.1.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "es5-ext": { + "version": "0.10.38", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", + "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38", + "es6-symbol": "3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "eslint": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-2.13.1.tgz", + "integrity": "sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "concat-stream": "1.6.0", + "debug": "2.6.9", + "doctrine": "1.5.0", + "es6-map": "0.1.5", + "escope": "3.6.0", + "espree": "3.5.2", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "1.3.1", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.7", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.17.1", + "is-resolvable": "1.1.0", + "js-yaml": "3.10.0", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "optionator": "0.8.2", + "path-is-absolute": "1.0.1", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.6.1", + "strip-json-comments": "1.0.4", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + } + } + }, + "eslint-config-airbnb-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-1.0.4.tgz", + "integrity": "sha1-vFQgiQvGRCzxUnfJpKKB/BMX+KY=", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz", + "integrity": "sha1-Wt2BBujJKNssuiMrzZ76hG49oWw=", + "dev": true, + "requires": { + "debug": "2.6.9", + "object-assign": "4.1.1", + "resolve": "1.5.0" + } + }, + "eslint-plugin-import": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-1.16.0.tgz", + "integrity": "sha1-svoH68xTUE0PKkR3WC7Iv/GHG58=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1", + "contains-path": "0.1.0", + "debug": "2.6.9", + "doctrine": "1.3.0", + "es6-map": "0.1.5", + "es6-set": "0.1.5", + "eslint-import-resolver-node": "0.2.3", + "has": "1.0.1", + "lodash.cond": "4.5.2", + "lodash.endswith": "4.2.1", + "lodash.find": "4.6.0", + "lodash.findindex": "4.6.0", + "minimatch": "3.0.4", + "object-assign": "4.1.1", + "pkg-dir": "1.0.0", + "pkg-up": "1.0.0" + }, + "dependencies": { + "doctrine": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.3.0.tgz", + "integrity": "sha1-E+dWgrVVGEJCdvfBc3g0Vu+RPSY=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + } + } + }, + "espree": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.2.tgz", + "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", + "dev": true, + "requires": { + "acorn": "5.3.0", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "express": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "requires": { + "accepts": "1.3.4", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.1.0", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "2.0.2", + "qs": "6.5.1", + "range-parser": "1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.1", + "serve-static": "1.13.1", + "setprototypeof": "1.1.0", + "statuses": "1.3.1", + "type-is": "1.6.15", + "utils-merge": "1.0.1", + "vary": "1.1.2" + }, + "dependencies": { + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "file-entry-cache": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.3.1.tgz", + "integrity": "sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" + }, + "dependencies": { + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "follow-redirects": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz", + "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk=", + "requires": { + "debug": "2.6.9", + "stream-consume": "0.1.0" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "formidable": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", + "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=" + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "generic-pool": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.0.tgz", + "integrity": "sha512-lYsIpjkyNBnRLKrcnJlXTEXB2ISmK9g7N4WqWwXbvr+tVB1+raaFnHoCYecWnuCo/XGHgM935WOWmr5Zx3tJKw==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-parameter-names": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/get-parameter-names/-/get-parameter-names-0.3.0.tgz", + "integrity": "sha1-LSI3zVkubFuFmrLv2rQ18Ajlu5c=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + }, + "hashring": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/hashring/-/hashring-3.2.0.tgz", + "integrity": "sha1-/aTv3oqiLNuX+x0qZeiEAeHBRM4=", + "requires": { + "connection-parse": "0.0.7", + "simple-lru-cache": "0.0.2" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.4.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "ignore": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.2.0", + "figures": "1.7.0", + "lodash": "4.17.4", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "ipaddr.js": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", + "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + }, + "is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-my-json-valid": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz", + "integrity": "sha512-Q2khNw+oBlWuaYvEEHtKSw/pCxD2L5Rc1C+UQme9X6JdRDh7m5D7HkozA0qa3DUkQ6VzCnEm8mVIQPyIRkI5sQ==", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-nan": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz", + "integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=", + "requires": { + "define-properties": "1.1.2" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isemail": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.2.1.tgz", + "integrity": "sha1-A1PT2aYpUQgMJiwqoKQrjqjp4qY=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "items": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/items/-/items-2.1.1.tgz", + "integrity": "sha1-i9FtnIOxlSneWuoyGsqtp4NkoZg=" + }, + "joi": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-9.2.0.tgz", + "integrity": "sha1-M4WseQGSEwy+Iw6ALsAskhW7/to=", + "requires": { + "hoek": "4.2.0", + "isemail": "2.2.1", + "items": "2.1.1", + "moment": "2.20.1", + "topo": "2.0.2" + } + }, + "js-string-escape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" + }, + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", + "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=" + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsonwebtoken": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-7.4.3.tgz", + "integrity": "sha1-d/UCHeBYtgWheD+hKD6ZgS5kVjg=", + "requires": { + "joi": "6.10.1", + "jws": "3.1.4", + "lodash.once": "4.1.1", + "ms": "2.0.0", + "xtend": "4.0.1" + }, + "dependencies": { + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "isemail": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", + "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=" + }, + "joi": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", + "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", + "requires": { + "hoek": "2.16.3", + "isemail": "1.2.0", + "moment": "2.20.1", + "topo": "1.1.0" + } + }, + "topo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/topo/-/topo-1.1.0.tgz", + "integrity": "sha1-6ddRYV0buH3IZdsYL6HKCl71NtU=", + "requires": { + "hoek": "2.16.3" + } + } + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jwa": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", + "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", + "requires": { + "base64url": "2.0.0", + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.9", + "safe-buffer": "5.1.1" + } + }, + "jwks-rsa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.2.1.tgz", + "integrity": "sha512-xg+fw7FOV4eGdDIEMqQJvPLmFv85h4uN+j/GKwJZAxlCrDQpM8ov1F709xKGEp/dG3l4TUxoSOeN6YK7+KpinQ==", + "requires": { + "@types/express-jwt": "0.0.34", + "debug": "2.6.9", + "limiter": "1.1.2", + "lru-memoizer": "1.11.1", + "ms": "2.0.0", + "request": "2.83.0" + } + }, + "jws": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", + "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", + "requires": { + "base64url": "2.0.0", + "jwa": "1.1.5", + "safe-buffer": "5.1.1" + } + }, + "le_node": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/le_node/-/le_node-1.7.1.tgz", + "integrity": "sha1-gxZAna2oK58pZXykBgZj+PEVUdE=", + "requires": { + "babel-runtime": "6.6.1", + "codependency": "0.1.4", + "json-stringify-safe": "5.0.1", + "lodash": "3.9.3", + "reconnect-core": "1.3.0", + "semver": "5.1.0" + }, + "dependencies": { + "lodash": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.9.3.tgz", + "integrity": "sha1-AVnoaDL+/8bWHYUrEqlTuZSWvTI=" + }, + "semver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", + "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=" + } + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "limiter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.2.tgz", + "integrity": "sha512-JIKZ0xb6fZZYa3deZ0BgXCgX6HgV8Nx3mFGeFHmFWW8Fb2c08e0CyE+G3nalpD0xGvGssjGb1UdFr+PprxZEbw==" + }, + "lock": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/lock/-/lock-0.1.4.tgz", + "integrity": "sha1-/sfervF+fDoKVeHaBCgD4l2RdF0=" + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "lodash.cond": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", + "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", + "dev": true + }, + "lodash.endswith": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.endswith/-/lodash.endswith-4.2.1.tgz", + "integrity": "sha1-/tWawXOO0+I27dcGTsRWRIs3vAk=", + "dev": true + }, + "lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=", + "dev": true + }, + "lodash.findindex": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz", + "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + }, + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=" + }, + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "lru-memoizer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-1.11.1.tgz", + "integrity": "sha1-BpP2EAWTkUwC4ZK/m42TiEy/UNM=", + "requires": { + "lock": "0.1.4", + "lodash": "4.5.1", + "lru-cache": "4.0.2", + "very-fast-args": "1.1.0" + }, + "dependencies": { + "lodash": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.5.1.tgz", + "integrity": "sha1-gOigdMpfOJOmscELKmNkktcQwxY=" + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "millisecond": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/millisecond/-/millisecond-0.1.2.tgz", + "integrity": "sha1-bMWtOGJByrjniv+WT4cCjuyS2sU=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz", + "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "moment": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" + }, + "moment-timezone": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", + "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", + "requires": { + "moment": "2.20.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "murmur-hash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmur-hash-js/-/murmur-hash-js-1.0.0.tgz", + "integrity": "sha1-UEEEkmnJZjPIZjhpYLL0KJ515bA=" + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "0.5.1", + "ncp": "2.0.0", + "rimraf": "2.4.5" + } + }, + "nan": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", + "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", + "optional": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "nice-simple-logger": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nice-simple-logger/-/nice-simple-logger-1.0.1.tgz", + "integrity": "sha1-D55khSe+e+PkmrdvqMjAmK+VG/Y=", + "requires": { + "lodash": "4.17.4" + } + }, + "no-kafka": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/no-kafka/-/no-kafka-3.2.8.tgz", + "integrity": "sha1-F2bgIb8z0iCGPbZBIKr3pQR6Cko=", + "requires": { + "@types/bluebird": "3.5.0", + "@types/lodash": "4.14.97", + "bin-protocol": "3.0.4", + "bluebird": "3.5.1", + "buffer-crc32": "0.2.13", + "hashring": "3.2.0", + "lodash": "4.16.4", + "murmur-hash-js": "1.0.0", + "nice-simple-logger": "1.0.1", + "wrr-pool": "1.1.3" + }, + "dependencies": { + "lodash": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.4.tgz", + "integrity": "sha1-Ac4wa5utExnypVKGdPiCl663ASc=" + } + } + }, + "node-schedule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.0.tgz", + "integrity": "sha512-NNwO9SUPjBwFmPh3vXiPVEhJLn4uqYmZYvJV358SRGM06BR4UoIqxJpeJwDDXB6atULsgQA97MfD1zMd5xsu+A==", + "requires": { + "cron-parser": "2.4.4", + "long-timeout": "0.1.1", + "sorted-array-functions": "1.1.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "packet-reader": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz", + "integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=" + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pg": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.4.1.tgz", + "integrity": "sha512-Pi5qYuXro5PAD9xXx8h7bFtmHgAQEG6/SCNyi7gS3rvb/ZQYDmxKchfB0zYtiSJNWq9iXTsYsHjrM+21eBcN1A==", + "requires": { + "buffer-writer": "1.0.1", + "js-string-escape": "1.0.1", + "packet-reader": "0.3.1", + "pg-connection-string": "0.1.3", + "pg-pool": "2.0.3", + "pg-types": "1.12.1", + "pgpass": "1.0.2", + "semver": "4.3.2" + } + }, + "pg-connection-string": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", + "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" + }, + "pg-pool": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.3.tgz", + "integrity": "sha1-wCIDLIlJ8xKk+R+2QJzgQHa+Mlc=" + }, + "pg-types": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz", + "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=", + "requires": { + "postgres-array": "1.0.2", + "postgres-bytea": "1.0.0", + "postgres-date": "1.0.3", + "postgres-interval": "1.1.1" + } + }, + "pgpass": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", + "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", + "requires": { + "split": "1.0.1" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + }, + "pkg-up": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", + "integrity": "sha1-Pgj7RhUlxEIWJKM7n35tCvWwWiY=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "postgres-array": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", + "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg=" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz", + "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" + }, + "postgres-interval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz", + "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==", + "requires": { + "xtend": "4.0.1" + } + }, + "precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "protocol-buffers-schema": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.3.2.tgz", + "integrity": "sha512-Xdayp8sB/mU+sUV4G7ws8xtYMGdQnxbeIfLjyO9TZZRJdztBGhlmbI5x1qcY4TG5hBkIKGnc28i7nXxaugu88w==" + }, + "proxy-addr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.5.2" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "mute-stream": "0.0.5" + } + }, + "reconnect-core": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reconnect-core/-/reconnect-core-1.3.0.tgz", + "integrity": "sha1-+65SkZp4d9hE4yRtAaLyZwHIM8g=", + "requires": { + "backoff": "2.5.0" + } + }, + "request": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.1", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "retry-as-promised": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", + "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", + "requires": { + "bluebird": "3.5.1", + "debug": "2.6.9" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "requires": { + "glob": "6.0.4" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "safe-json-stringify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz", + "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=", + "optional": true + }, + "semver": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" + }, + "send": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "requires": { + "debug": "2.6.9", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "fresh": "0.5.2", + "http-errors": "1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + }, + "dependencies": { + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, + "sequelize": { + "version": "4.32.2", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.32.2.tgz", + "integrity": "sha512-BuwzHVbbaVc+EajXM2MhKLN0GRVC+XGJCFzBFuh7kVOur8NrPo/4X5eTnXnx9+a4Co9+qCP42PpHF90BPEkaUw==", + "requires": { + "bluebird": "3.5.1", + "cls-bluebird": "2.1.0", + "debug": "3.1.0", + "depd": "1.1.2", + "dottie": "2.0.0", + "generic-pool": "3.4.0", + "inflection": "1.12.0", + "lodash": "4.17.4", + "moment": "2.20.1", + "moment-timezone": "0.5.14", + "retry-as-promised": "2.3.2", + "semver": "5.5.0", + "terraformer-wkt-parser": "1.1.2", + "toposort-class": "1.0.1", + "uuid": "3.2.1", + "validator": "9.3.0", + "wkx": "0.4.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + } + } + }, + "serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "requires": { + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.16.1" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "shelljs": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz", + "integrity": "sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg=", + "dev": true + }, + "shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag==" + }, + "simple-lru-cache": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz", + "integrity": "sha1-1ZzDoZPBpdAyD4Tucy9uRxPlEd0=" + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.2.0" + } + }, + "sorted-array-functions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.1.0.tgz", + "integrity": "sha512-zq6fLdGQixb9VZfT/tLgU+LzoedJyTbcf1I/TKETFeUVoWIfcs5HNr+SJSvQJLXRlEZjB1gpILTrxamxAdCcgA==" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2.3.8" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "stream-consume": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz", + "integrity": "sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "superagent": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", + "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", + "requires": { + "component-emitter": "1.2.1", + "cookiejar": "2.1.1", + "debug": "3.1.0", + "extend": "3.0.1", + "form-data": "2.3.1", + "formidable": "1.1.1", + "methods": "1.1.2", + "mime": "1.4.1", + "qs": "6.5.1", + "readable-stream": "2.3.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.4", + "slice-ansi": "0.0.4", + "string-width": "2.1.1" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "tc-core-library-js": { + "version": "github:gondzo/tc-core-library-js#38bb886089822b96726180179c01cedc1291bef3", + "requires": { + "axios": "0.12.0", + "bunyan": "1.8.12", + "config": "1.29.2", + "jsonwebtoken": "7.4.3", + "jwks-rsa": "1.2.1", + "le_node": "1.7.1", + "lodash": "4.17.4", + "millisecond": "0.1.2" + } + }, + "terraformer": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.8.tgz", + "integrity": "sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM=", + "requires": { + "@types/geojson": "1.0.6" + } + }, + "terraformer-wkt-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.1.2.tgz", + "integrity": "sha1-M2oMj8gglKWv+DKI9prt7NNpvww=", + "requires": { + "terraformer": "1.0.8" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "topo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", + "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", + "requires": { + "hoek": "4.2.0" + } + }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.17" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "validator": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.3.0.tgz", + "integrity": "sha512-E+dZnwgfBac9vEoswKlULQMoEByr7gVB8xL+1/3VgeGCBPvfIa0I8GAejtZHP1b7EXBmOQwXvDOSelrDfB6eig==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "very-fast-args": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/very-fast-args/-/very-fast-args-1.1.0.tgz", + "integrity": "sha1-4W0dH6+KbllqJGQh/ZCneWPQs5Y=" + }, + "winston": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.0.tgz", + "integrity": "sha1-gIBQuT1SZh7Z+2wms/DIJnCLCu4=", + "requires": { + "async": "1.0.0", + "colors": "1.0.3", + "cycle": "1.0.3", + "eyes": "0.1.8", + "isstream": "0.1.2", + "stack-trace": "0.0.10" + } + }, + "wkx": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.2.tgz", + "integrity": "sha1-d201pjSlwi5lbkdEvetU+D/Szo0=", + "requires": { + "@types/node": "9.4.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "wrr-pool": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wrr-pool/-/wrr-pool-1.1.3.tgz", + "integrity": "sha1-/a0i8uofMDY//l14HPeUl6d/8H4=", + "requires": { + "lodash": "4.17.4" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..aabeec4 --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "tc-email", + "version": "1.0.0", + "description": "Topcoder Email Services", + "main": "index.js", + "scripts": { + "start": "node connect/connectEmailServer", + "test": "NODE_ENV=test mocha", + "lint": "eslint *.js src config connect || true", + "generate-tokens": "node generate-tokens.js" + }, + "author": "TCSCODER", + "license": "none", + "devDependencies": { + "chai": "^4.1.2", + "eslint": "^2.8.0", + "eslint-config-airbnb-base": "^1.0.1", + "eslint-plugin-import": "^1.5.0", + "mocha": "^5.0.0" + }, + "dependencies": { + "@sendgrid/client": "^6.2.1", + "@sendgrid/mail": "^6.2.1", + "bluebird": "^3.5.1", + "body-parser": "^1.15.2", + "co": "^4.6.0", + "config": "^1.29.2", + "console.history": "^1.5.0", + "cors": "^2.7.1", + "express": "^4.14.0", + "get-parameter-names": "^0.3.0", + "joi": "^9.0.4", + "jwks-rsa": "^1.2.1", + "lodash": "^4.17.4", + "millisecond": "^0.1.2", + "no-kafka": "^3.2.4", + "node-schedule": "^1.3.0", + "pg": "^7.3.0", + "sequelize": "^4.21.0", + "superagent": "^3.8.0", + "tc-core-library-js": "gondzo/tc-core-library-js.git#dev", + "winston": "^2.4.0" + }, + "engines": { + "node": "8.7.0" + } +} diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..c9d2f1c --- /dev/null +++ b/src/app.js @@ -0,0 +1,250 @@ +/** + * The application entry point + */ +'use strict'; + +require('./bootstrap'); + +const config = require('config'); +const jwtAuth = require('tc-core-library-js').middleware.jwtAuthenticator; +const express = require('express'); +const _ = require('lodash'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const Kafka = require('no-kafka'); +const schedule = require('node-schedule'); +const helper = require('./common/helper'); +const logger = require('./common/logger'); +const errors = require('./common/errors'); +const models = require('./models'); + +let emailTries = {}; + +/** + * Configure Kafka consumer. + * @param {Object} handlers the handlers + */ +function configureKafkaConsumer(handlers) { + // create group consumer + const options = { groupId: config.KAFKA_GROUP_ID, connectionString: config.KAFKA_URL }; + if (config.KAFKA_CLIENT_CERT && config.KAFKA_CLIENT_CERT_KEY) { + options.ssl = { cert: config.KAFKA_CLIENT_CERT, key: config.KAFKA_CLIENT_CERT_KEY }; + } + const pauseTime = parseInt(config.EMAIL_PAUSE_TIME); + const maxErrors = parseInt(config.EMAIL_MAX_ERRORS); + const consumer = new Kafka.SimpleConsumer(options); + + // data handler + const dataHandler = (messageSet, topic, partition) => Promise.each(messageSet, (m) => { + const message = m.message.value.toString('utf8'); + logger.info(`Handle Kafka event message; Topic: ${topic}; Partition: ${partition}; Offset: ${m.offset}; Message: ${message}.`); + // ignore configured Kafka topic prefix + let topicName = topic; + if (config.KAFKA_TOPIC_IGNORE_PREFIX && topicName.startsWith(config.KAFKA_TOPIC_IGNORE_PREFIX)) { + topicName = topicName.substring(config.KAFKA_TOPIC_IGNORE_PREFIX.length); + } + // find handler + const handler = handlers[topicName]; + if (!handler) { + logger.info(`No handler configured for topic: ${topicName}`); + // return null to ignore this message + return null; + } + let emailModel = {}; + const messageJSON = JSON.parse(message); + const handlerAsync = Promise.promisify(handler); + // use handler to create notification instances for each recipient + return models.Email.create( + Object.assign({ status: 'PENDING' }, { + topicName, + data: JSON.stringify(messageJSON.data), + recipients: JSON.stringify(messageJSON.recipients), + }) + ).then((model) => { + emailModel = model; + return handlerAsync(topicName, messageJSON); + }).then((result) => { // save email + const now = new Date(); + logger.log('info', 'Email sent', { + sender: 'Connect', + from_address: messageJSON.recipients.join(','), + to_address: config.EMAIL_FROM, + status: result.success ? 'Message accepted' : 'Message rejected', + }); + if (result.success) { + emailTries[topicName] = 0; + emailModel.status = 'SUCCESS'; + return emailModel.save(); + } else { + // emailTries[topicName] += 1; //temporary disabling this feature + emailModel.status = 'FAILED'; + return emailModel.save().then(() => { + /* + * temporary disabling this feature as there is chance of losing message during + * unsubscribe/pausing due to simple kafka consumer + */ + /* + const currentTries = emailTries[topicName]; + if (currentTries > maxErrors) { + logger.debug(`Failed to send email. Will sleep for ${pauseTime}s`); + emailTries[topicName] = 0; + + schedule.scheduleJob(new Date(now.getTime() + pauseTime * 1000), () => { + consumer.subscribe(topic, dataHandler); + }); + + return consumer.unsubscribe(topic, partition).then(() => { + throw result.error + }); + } else { + logger.debug(`Failed to send email (retries left ${maxErrors - currentTries})`); + throw result.error; + }*/ + }); + } + }).then(() => consumer.commitOffset({ topic, partition, offset: m.offset })) // commit offset + .catch((err) => logger.error(err)); + }); + + return startKafkaConsumer(consumer, handlers, dataHandler); +} + +/** + * Start Kafka consumer. + * @param {Object} consumer the kafka consumer + * @param {Object} handlers the handlers map + * @param {Object} dataHandler the kafka data handler function + */ +function startKafkaConsumer(consumer, handlers, dataHandler) { + /*const strategies = [{ + subscriptions: _.keys(handlers), + handler: dataHandler + }]; + return consumer.init(strategies);*/ + return consumer + .init() + .then(() => Promise.each(_.keys(handlers), (topicName) => { // add back the ignored topic prefix to use full topic name + emailTries[topicName] = 0; + return consumer.subscribe(`${config.KAFKA_TOPIC_IGNORE_PREFIX || ''}${topicName}`, dataHandler); + }) + ); +} + +/** + * Callback to retry sending email. + * @param {Object} handlers the handlers + */ +function retryEmail(handlers) { + return models.Email.findAll({ where: { status: 'FAILED' } }).then((models) => { + if (models.length > 0) { + logger.info(`Found ${models.length} e-mails to be resent`); + return Promise.each(models, (m) => { + // find handler + const handler = handlers[m.topicName]; + if (!handler) { + logger.warn(`No handler configured for topic: ${m.topicName}`); + return m; + } + const handlerAsync = Promise.promisify(handler); + const messageJSON = { data: JSON.parse(m.data), recipients: JSON.parse(m.recipients) }; + return handlerAsync(m.topicName, messageJSON).then((result) => { // save email + if (result.success) { + logger.info(`Email model with ${m.id} was sent correctly`); + m.status = 'SUCCESS'; + return m.save(); + } + logger.info(`Email model with ${m.id} wasn't sent correctly`); + return m; + }); + }); + } else { + return models; + } + }); +} + +/** + * Start the email server. + * @param {Object} handlers the handlers + */ +function start(handlers) { + const app = express(); + app.set('port', config.PORT); + + app.use(cors()); + app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({ extended: true })); + + const apiRouter = express.Router(); + + // load all routes + _.each(require('./routes'), (verbs, url) => { + _.each(verbs, (def, verb) => { + const actions = []; + const method = require('./controllers/' + def.controller)[def.method]; + if (!method) { + throw new Error(def.method + ' is undefined'); + } + actions.push((req, res, next) => { + req.signature = `${def.controller}#${def.method}`; + next(); + }); + if (url !== '/email/health') { + actions.push(jwtAuth()); + actions.push((req, res, next) => { + if (!req.authUser) { + return next(new errors.UnauthorizedError('Authorization failed.')); + } + req.user = req.authUser; + return next(); + }); + } + actions.push(method); + apiRouter[verb](url, helper.autoWrapExpress(actions)); + }); + }); + + app.use('/', apiRouter); + + app.use((req, res) => { + res.status(404).json({ error: 'route not found' }); + }); + + app.use((err, req, res, next) => { // eslint-disable-line + logger.logFullError(err, req.signature); + let status = err.httpStatus || 500; + if (err.isJoi) { + status = 400; + } + // from express-jwt + if (err.name === 'UnauthorizedError') { + status = 401; + } + res.status(status); + if (err.isJoi) { + res.json({ + error: 'Validation failed', + details: err.details, + }); + } else { + res.json({ + error: err.message, + }); + } + }); + + return models + .init() + .then(() => configureKafkaConsumer(handlers)) + .then(() => { + return app.listen(app.get('port'), () => { + logger.info(`Express server listening on port ${app.get('port')}`); + }); + }); +} + +// Exports +module.exports = { + start, + retryEmail, +}; diff --git a/src/bootstrap.js b/src/bootstrap.js new file mode 100644 index 0000000..38cc33d --- /dev/null +++ b/src/bootstrap.js @@ -0,0 +1,10 @@ +/** + * Init app + */ + +'use strict'; + +global.Promise = require('bluebird'); +const logger = require('./common/logger'); + +logger.buildService(require('./services/TemplateService')); diff --git a/src/common/errors.js b/src/common/errors.js new file mode 100644 index 0000000..c987e82 --- /dev/null +++ b/src/common/errors.js @@ -0,0 +1,41 @@ +/** + * This file defines application errors. + */ +'use strict'; + +const util = require('util'); + +/** + * Helper function to create generic error object with http status code + * @param {String} name the error name + * @param {Number} statusCode the http status code + * @returns {Function} the error constructor + * @private + */ +function _createError(name, statusCode) { + /** + * The error constructor + * @param {String} message the error message + * @param {String} [cause] the error cause + * @constructor + */ + function ErrorCtor(message, cause) { + Error.call(this); + Error.captureStackTrace(this); + this.message = message || name; + this.cause = cause; + this.httpStatus = statusCode; + } + + util.inherits(ErrorCtor, Error); + ErrorCtor.prototype.name = name; + return ErrorCtor; +} + +module.exports = { + BadRequestError: _createError('BadRequestError', 400), + ValidationError: _createError('ValidationError', 400), + UnauthorizedError: _createError('UnauthorizedError', 401), + ForbiddenError: _createError('ForbiddenError', 403), + NotFoundError: _createError('NotFoundError', 404), +}; diff --git a/src/common/helper.js b/src/common/helper.js new file mode 100644 index 0000000..c788f72 --- /dev/null +++ b/src/common/helper.js @@ -0,0 +1,44 @@ +/** + * Contains generic helper methods + */ +'use strict'; + +const _ = require('lodash'); +const co = require('co'); + +/** + * Wrap generator function to standard express function + * @param {Function} fn the generator function + * @returns {Function} the wrapped function + */ +function wrapExpress(fn) { + return function (req, res, next) { + co(fn(req, res, next)).catch(next); + }; +} + +/** + * Wrap all generators from object + * @param obj the object (controller exports) + * @returns {Object|Array} the wrapped object + */ +function autoWrapExpress(obj) { + if (_.isArray(obj)) { + return obj.map(autoWrapExpress); + } + if (_.isFunction(obj)) { + if (obj.constructor.name === 'GeneratorFunction') { + return wrapExpress(obj); + } + return obj; + } + _.each(obj, (value, key) => { + obj[key] = autoWrapExpress(value); + }); + return obj; +} + +module.exports = { + wrapExpress, + autoWrapExpress, +}; diff --git a/src/common/logger.js b/src/common/logger.js new file mode 100644 index 0000000..0927b71 --- /dev/null +++ b/src/common/logger.js @@ -0,0 +1,152 @@ +/** + * This module contains the winston logger configuration. + */ +'use strict'; + +const _ = require('lodash'); +const Joi = require('joi'); +const winston = require('winston'); +const util = require('util'); +const config = require('config'); +const getParams = require('get-parameter-names'); + +const transports = []; + +transports.push(new (winston.transports.Console)({ level: config.LOG_LEVEL })); + +if (config.DISABLE_LOGGING == 'false') { + transports.push(new (winston.transports.File)({ + filename: 'log.txt', + timestamp: true, + json: true, + })); +} + +const logger = new (winston.Logger)({ transports }); + +/** + * Log error details with signature + * @param err the error + * @param signature the signature + */ +logger.logFullError = function (err, signature) { // eslint-disable-line + if (!err) { + return; + } + if (signature) { + logger.error(signature); + } + const args = Array.prototype.slice.call(arguments); + args.shift(); + logger.error.apply(logger, args); + logger.error(util.inspect(err)); + if (!err.logged) { + logger.error(err.stack); + } + err.logged = true; +}; + +/** + * Sanitize object. + * @param {Object} obj the object + * @returns {Object} the new object with removed properties + * @private + */ +function _sanitizeObject(obj) { + try { + return JSON.parse(JSON.stringify(obj, (name, value) => { + if (_.isArray(value) && value.length > 30) { + return 'Array(' + value.length + ')'; + } + return value; + })); + } catch (e) { + return obj; + } +} + +/** + * Convert array with arguments to object + * @param {Array} params the name of parameters + * @param {Array} arr the array with values + * @private + */ +function _combineObject(params, arr) { + const ret = {}; + _.each(arr, (arg, i) => { + ret[params[i]] = arg; + }); + return ret; +} + +/** + * Decorate all functions of a service and log debug information if DEBUG is enabled + * @param {Object} service the service + */ +logger.decorateWithLogging = function (service) { + if (config.LOG_LEVEL !== 'debug') { + return; + } + _.each(service, (method, name) => { + const params = method.params || getParams(method); + service[name] = function*() { + logger.debug('ENTER ' + name); + logger.debug('input arguments'); + const args = Array.prototype.slice.call(arguments); + logger.debug(util.inspect(_sanitizeObject(_combineObject(params, args)))); + try { + const result = yield* method.apply(this, arguments); + logger.debug('EXIT ' + name); + logger.debug('output arguments'); + if (result !== null && result !== undefined) { + logger.debug('output arguments'); + logger.debug(util.inspect(_sanitizeObject(result))); + } + return result; + } catch (e) { + logger.logFullError(e, name); + throw e; + } + }; + }); +}; + +/** + * Decorate all functions of a service and validate input values + * and replace input arguments with sanitized result form Joi + * Service method must have a `schema` property with Joi schema + * @param {Object} service the service + */ +logger.decorateWithValidators = function (service) { + _.each(service, (method, name) => { + if (!method.schema) { + return; + } + const params = getParams(method); + service[name] = function*() { + const args = Array.prototype.slice.call(arguments); + const value = _combineObject(params, args); + const normalized = Joi.attempt(value, method.schema); + const newArgs = []; + // Joi will normalize values + // for example string number '1' to 1 + // if schema type is number + _.each(params, (param) => { + newArgs.push(normalized[param]); + }); + return yield method.apply(this, newArgs); + }; + service[name].params = params; + }); +}; + +/** + * Apply logger and validation decorators + * @param {Object} service the service to wrap + */ +logger.buildService = function (service) { + logger.decorateWithValidators(service); + logger.decorateWithLogging(service); +}; + +module.exports = logger; diff --git a/src/controllers/HealthController.js b/src/controllers/HealthController.js new file mode 100644 index 0000000..871380f --- /dev/null +++ b/src/controllers/HealthController.js @@ -0,0 +1,18 @@ +/** + * Contains endpoints related to service health + */ +'use strict'; + +/** + * Health Check. + * @param req the request + * @param res the response + */ +function health(req, res) { + res.json({ health: 'ok' }); +} + +// Exports +module.exports = { + health, +}; diff --git a/src/controllers/TemplateController.js b/src/controllers/TemplateController.js new file mode 100644 index 0000000..ea8c18a --- /dev/null +++ b/src/controllers/TemplateController.js @@ -0,0 +1,20 @@ +/** + * Contains endpoints related to template controller + */ +'use strict'; + +const TemplateService = require('../services/TemplateService'); + +/** + * Get list with email template placeholder names. + * @param req the request + * @param res the response + */ +function* eventTypes(req, res) { + res.json(yield TemplateService.listPlaceholders(req.params.name)); +} + +// Exports +module.exports = { + eventTypes, +}; diff --git a/src/models/Email.js b/src/models/Email.js new file mode 100644 index 0000000..2b1760b --- /dev/null +++ b/src/models/Email.js @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2018 TopCoder Inc., All Rights Reserved. + */ + +/** + * the email schema + * + * @author TCSCODER + * @version 1.0 + */ + + +module.exports = (sequelize, DataTypes) => sequelize.define('Email', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + topicName: { type: DataTypes.STRING, allowNull: true, field: 'topic_name' }, + data: { type: DataTypes.STRING, allowNull: false }, + recipients: { type: DataTypes.STRING, allowNull: false }, + status: { type: DataTypes.STRING, allowNull: false }, +}, {}); + +// sequelize will generate and manage createdAt, updatedAt fields diff --git a/src/models/datasource.js b/src/models/datasource.js new file mode 100644 index 0000000..8cbf517 --- /dev/null +++ b/src/models/datasource.js @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2018 Topcoder Inc., All Rights Reserved. + */ + +/** + * Init datasource + * + * @author TCSCODER + * @version 1.0 + */ + +const config = require('config'); +const Sequelize = require('sequelize'); +const logger = require('../common/logger'); + +Sequelize.Promise = require('bluebird'); +let sequelizeInstance = null; + +/** + * get sequelize instance + */ +function getSequelize() { + if (!sequelizeInstance) { + sequelizeInstance = new Sequelize(config.DATABASE_URL, config.DATABASE_OPTIONS); + sequelizeInstance + .authenticate() + .then(() => { + logger.info('Database connection has been established successfully.'); + }) + .catch((err) => { + logger.error('Unable to connect to the database:', err); + }); + } + return sequelizeInstance; +} + +module.exports = { + getSequelize, +}; diff --git a/src/models/index.js b/src/models/index.js new file mode 100644 index 0000000..1efed97 --- /dev/null +++ b/src/models/index.js @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2018 TopCoder Inc., All Rights Reserved. + */ + +/** + * the sequelize schema index + * + * @author TCSCODER + * @version 1.0 + */ + +const sequelize = require('./datasource').getSequelize(); +const DataTypes = require('sequelize/lib/data-types'); + +const Email = require('./Email')(sequelize, DataTypes); + +module.exports = { + Email, + init: () => sequelize.sync(), +}; diff --git a/src/routes.js b/src/routes.js new file mode 100644 index 0000000..c8db8ef --- /dev/null +++ b/src/routes.js @@ -0,0 +1,16 @@ +'use strict'; + +module.exports = { + '/email/templates/eventType/:name': { + get: { + controller: 'TemplateController', + method: 'eventTypes', + }, + }, + '/email/health': { + get: { + controller: 'HealthController', + method: 'health', + }, + }, +}; diff --git a/src/services/TemplateService.js b/src/services/TemplateService.js new file mode 100644 index 0000000..493e729 --- /dev/null +++ b/src/services/TemplateService.js @@ -0,0 +1,79 @@ +/** + * Service for email template placeholders. + */ + +'use strict'; + +const _ = require('lodash'); +const client = require('@sendgrid/client'); +const config = require('config'); +const Joi = require('joi'); +const errors = require('../common/errors'); +const logger = require('../common/logger'); + +// set api key for SendGrid email client +client.setApiKey(config.SENDGRID_API_KEY); + +/** + * Find placeholders inside html template string. + * + * @param {String} html the html string with template + * @return {Array} the list of placeholders names + */ +function* _findPlaceholders(html) { + const pattern = /{{(.*?)}}/g; + const placeholders = []; + let match = pattern.exec(html); + while (match != null) { + // ignore duplicates and empty placeholder + if (match[1].length > 0 && !_.includes(placeholders, match[1])) { + placeholders.push(match[1]); + } + match = pattern.exec(html); + } + return placeholders; +} + +/** + * Get email template placeholder name. + * + * @param {String} name the Kafka topic name + * @return {Array} the list of email template placeholder names + */ +function* listPlaceholders(name) { + const templateId = config.TEMPLATE_MAP[name]; + if (templateId === undefined) { + throw new errors.BadRequestError(`Topic ${name} was not found`); + } + + try { + const response = yield client.request({ + method: 'GET', + url: '/v3/templates/' + templateId, + }); + + const template = response[0].body; + if (template.versions.length === 0) { + throw new errors.NotFoundError(`Template with id ${templateId} has no version in SendGrid`); + } + logger.debug(template.versions[0]); + + return yield _findPlaceholders(template.versions[0].html_content + template.versions[0].subject); + } catch (err) { + if (err.code === 404) { + throw new errors.NotFoundError(`Template with id ${templateId} for topic ${name} not found in SendGrid`); + } else if (err.code === 403) { + throw new errors.ForbiddenError('Sendgrid API key is invalid'); + } else { + throw err; + } + } +} + +listPlaceholders.schema = { + name: Joi.string().required(), +}; + +module.exports = { + listPlaceholders, +}; diff --git a/test/emailServiceTest.js b/test/emailServiceTest.js new file mode 100644 index 0000000..2b25a0f --- /dev/null +++ b/test/emailServiceTest.js @@ -0,0 +1,96 @@ +/** + * Email test. + */ +'use strict'; + +global.Promise = require('bluebird'); + +const _ = require('lodash'); +const sgMail = require('@sendgrid/mail'); +const config = require('config'); +const assert = require('chai').assert; +const Kafka = require('no-kafka'); + +const emailServer = require('../index'); +const service = require('../connect/service'); + +let globalDone = null; + +const defaultHandler = (topic, message, callback) => { + const templateId = config.TEMPLATE_MAP[topic]; + if (templateId === undefined) { + callback(null, { success: false, error: `Template not found for topic ${topic}` }); + return finish(`Template not found for topic ${topic}`); + } + + // send email + const replyTo = message.replyTo ? message.replyTo : config.EMAIL_FROM; + service.sendEmail(templateId, message.recipients, message.data).then(() => { + callback(null, { success: true }); + finish(null); + }).catch((err) => { + callback(null, { success: false, error: err }); + finish(err); + }); +}; + +// init all events +_.keys(config.TEMPLATE_MAP).forEach((eventType) => { + emailServer.addTopicHandler(eventType, defaultHandler); +}); + +const finish = (err) => { + if (globalDone) { + setTimeout(() => { + globalDone(err); + globalDone = null; + }, 10); + } +} + +const successTestMessage = { + data: { + title: 'Title', + value: 'Value', + subject: 'Subject' + }, + recipients: [ 'invalid@invalid.tt' ] +} + +describe('Email Test', () => { + let producer; + + before(() => { + producer = new Kafka.Producer(); + return producer.init().then(() => emailServer.start()); + }); + + after(function () { + return producer.end(); + }); + + describe('Created', () => { + it('success', (done) => { + globalDone = done; + producer.send({ + topic: `${config.KAFKA_TOPIC_PREFIX}email.project.created`, + message: { + value: JSON.stringify(successTestMessage) + } + }); + }); + }); + + describe('Updated', () => { + it('success', (done) => { + globalDone = done; + producer.send({ + topic: `${config.KAFKA_TOPIC_PREFIX}email.project.updated`, + message: { + value: JSON.stringify(successTestMessage) + } + }); + }); + }); + +}); diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..8d21387 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,2 @@ +--timeout 10000 +--exit