diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/README.md b/README.md index c01320c..d7327b9 100755 --- a/README.md +++ b/README.md @@ -21,32 +21,49 @@ Configuration for the application is at `config/default.js` and `config/production.js`. The following parameters can be set in config files or in env variables: -- LOG_LEVEL: the log level -- PORT: the server port -- AUTH_SECRET: TC Authentication secret -- VALID_ISSUERS: valid issuers for TC authentication -- PAGE_SIZE: the default pagination limit -- MAX_PAGE_SIZE: the maximum pagination size -- API_VERSION: the API version -- DB_NAME: the database name -- DB_USERNAME: the database username -- DB_PASSWORD: the database password -- DB_HOST: the database host -- DB_PORT: the database port -- ES_HOST: Elasticsearch host -- ES_REFRESH: Should elastic search refresh. Default is 'true'. Values can be 'true', 'wait_for', 'false' -- ELASTICCLOUD_ID: The elastic cloud id, if your elasticsearch instance is hosted on elastic cloud. DO NOT provide a value for ES_HOST if you are using this -- ELASTICCLOUD_USERNAME: The elastic cloud username for basic authentication. Provide this only if your elasticsearch instance is hosted on elastic cloud -- ELASTICCLOUD_PASSWORD: The elastic cloud password for basic authentication. Provide this only if your elasticsearch instance is hosted on elastic cloud -- ES.DOCUMENTS: Elasticsearch index, type and id mapping for resources. -- SKILL_INDEX: The Elastic search index for skill. Default is `skill` -- SKILL_ENRICH_POLICYNAME: The enrich policy for skill. Default is `skill-policy` -- TAXONOMY_INDEX: The Elastic search index for taxonomy. Default is `taxonomy` -- TAXONOMY_PIPELINE_ID: The pipeline id for enrichment with taxonomy. Default is `taxonomy-pipeline` -- TAXONOMY_ENRICH_POLICYNAME: The enrich policy for taxonomy. Default is `taxonomy-policy` -- MAX_BATCH_SIZE: Restrict number of records in memory during bulk insert (Used by the db to es migration script) -- MAX_BULK_SIZE: The Bulk Indexing Maximum Limits. Default is `100` (Used by the db to es migration script) +- `LOG_LEVEL`: the log level +- `PORT`: the server port +- `AUTH_SECRET`: TC Authentication secret +- `VALID_ISSUERS`: valid issuers for TC authentication +- `PAGE_SIZE`: the default pagination limit +- `MAX_PAGE_SIZE`: the maximum pagination size +- `API_VERSION`: the API version +- `DB_NAME`: the database name +- `DB_USERNAME`: the database username +- `DB_PASSWORD`: the database password +- `DB_HOST`: the database host +- `DB_PORT`: the database port +- `ES_HOST`: Elasticsearch host +- `ES_REFRESH`: Should elastic search refresh. Default is 'true'. Values can be 'true', 'wait_for', 'false' +- `ELASTICCLOUD_ID`: The elastic cloud id, if your elasticsearch instance is hosted on elastic cloud. DO NOT provide a value for ES_HOST if you are using this +- `ELASTICCLOUD_USERNAME`: The elastic cloud username for basic authentication. Provide this only if your elasticsearch instance is hosted on elastic cloud +- `ELASTICCLOUD_PASSWORD`: The elastic cloud password for basic authentication. Provide this only if your elasticsearch instance is hosted on elastic cloud +- `ES`.DOCUMENTS: Elasticsearch index, type and id mapping for resources. +- `SKILL_INDEX`: The Elastic search index for skill. Default is `skill` +- `TAXONOMY_INDEX`: The Elastic search index for taxonomy. Default is `taxonomy` +- `MAX_BATCH_SIZE`: Restrict number of records in memory during bulk insert (Used by the db to es migration script) +- `MAX_BULK_SIZE`: The Bulk Indexing Maximum Limits. Default is `100` (Used by the db to es migration script) + +- `AUTH0_URL`: Auth0 URL, used to get TC M2M token +- `AUTH0_AUDIENCE`: Auth0 audience, used to get TC M2M token +- `TOKEN_CACHE_TIME`: Auth0 token cache time, used to get TC M2M token +- `AUTH0_CLIENT_ID`: Auth0 client id, used to get TC M2M token +- `AUTH0_CLIENT_SECRET`: Auth0 client secret, used to get TC M2M token +- `AUTH0_PROXY_SERVER_URL`: Proxy Auth0 URL, used to get TC M2M token + +- `BUSAPI_URL`: Topcoder Bus API URL +- `KAFKA_ERROR_TOPIC`: The error topic at which bus api will publish any errors +- `KAFKA_MESSAGE_ORIGINATOR`: The originator value for the kafka messages +- `SKILLS_ERROR_TOPIC`: Kafka topic for report operation error + +**NOTE** AUTH0 related configuration normally is shared on challenge forum. + +## DB and Elasticsearch In Docker +- Navigate to the directory `docker-pgsql-es` folder. Rename `sample.env` to `.env` and change any values if required. +- Run `docker-compose up -d` to have docker instances of pgsql and elasticsearch to use with the api +**NOTE** To completely restart the services, run `docker-compose down --volumes` and then `docker-compose up`. +Notice the `--volumes` argument is passed to the `docker-compose down` command to remove the volume that stores DB data. Without the `--volumes` argument the DB data will be persistent after the services are put down. ## Local deployment @@ -58,17 +75,16 @@ Setup your Postgresql DB and Elasticsearch instance and ensure that they are up - Run the migrations - `npm run migrations up`. This will create the tables. - Then run `npm run insert-data` and insert mock data into the database. - Run `npm run migrate-db-to-es` to sync data with ES. -- Startup server `npm run start` +- Startup server `npm run start:dev` ## Migrations Migrations are located under the `./scripts/db/` folder. Run `npm run migrations up` and `npm run migrations down` to execute the migrations or remove the earlier ones ## Local Deployment with Docker +Setup your Postgresql DB and Elasticsearch instance and ensure that they are up and running. -- Navigate to the directory `docker-pgsql-es` folder. Rename `sample.env` to `.env` and change any values if required. -- Run `docker-compose up -d` to have docker instances of pgsql and elasticsearch to use with the api - +- Configure AUTH0 related parameters via ENV variables. Note that normally you don't need to change other configuration. - Create database using `npm run create-db`. - Run the migrations - `npm run migrations up`. This will create the tables. - Then run `npm run insert-data` and insert mock data into the database. @@ -102,6 +118,8 @@ Migrations are located under the `./scripts/db/` folder. Run `npm run migrations | `npm run delete-data` | Delete the data from the database | | `npm run migrations up` | Run up migration | | `npm run migrations down` | Run down migration | +| `npm run create-index` | Create Elasticsearch indexes. Use `-- --force` flag to skip confirmation | +| `npm run delete-index` | Delete Elasticsearch indexes. Use `-- --force` flag to skip confirmation | | `npm run generate:doc:permissions` | Generate [permissions.html](docs/permissions.html) | | `npm run generate:doc:permissions:dev` | Generate [permissions.html](docs/permissions.html) on any changes (useful during development). | diff --git a/config/default.js b/config/default.js index 1554127..42c4df4 100755 --- a/config/default.js +++ b/config/default.js @@ -20,6 +20,20 @@ module.exports = { DB_HOST: process.env.DB_HOST || 'localhost', DB_PORT: process.env.DB_PORT || 5432, + AUTH0_URL: process.env.AUTH0_URL, + AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE, + TOKEN_CACHE_TIME: process.env.TOKEN_CACHE_TIME, + AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID, + AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET, + AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL, + + BUSAPI_URL: process.env.BUSAPI_URL || 'https://api.topcoder-dev.com/v5', + + KAFKA_ERROR_TOPIC: process.env.KAFKA_ERROR_TOPIC || 'common.error.reporting', + KAFKA_MESSAGE_ORIGINATOR: process.env.KAFKA_MESSAGE_ORIGINATOR || 'skills-api', + + SKILLS_ERROR_TOPIC: process.env.SKILLS_ERROR_TOPIC || 'skills.action.error', + // ElasticSearch ES: { HOST: process.env.ES_HOST || 'http://localhost:9200', @@ -36,14 +50,11 @@ module.exports = { DOCUMENTS: { skill: { index: process.env.SKILL_INDEX || 'skill', - type: '_doc', - enrichPolicyName: process.env.SKILL_ENRICH_POLICYNAME || 'skill-policy' + type: '_doc' }, taxonomy: { index: process.env.TAXONOMY_INDEX || 'taxonomy', - type: '_doc', - pipelineId: process.env.TAXONOMY_PIPELINE_ID || 'taxonomy-pipeline', - enrichPolicyName: process.env.TAXONOMY_ENRICH_POLICYNAME || 'taxonomy-policy' + type: '_doc' } }, MAX_BATCH_SIZE: parseInt(process.env.MAX_BATCH_SIZE, 10) || 10000, diff --git a/docker/sample.env b/docker/sample.env index 6cb7442..37522b8 100644 --- a/docker/sample.env +++ b/docker/sample.env @@ -6,3 +6,8 @@ DB_PORT=5432 ES_HOST=http://host.docker.internal:9200 PORT=3001 + +AUTH0_CLIENT_ID= +AUTH0_CLIENT_SECRET= +AUTH0_URL= +AUTH0_AUDIENCE= diff --git a/docs/permissions.html b/docs/permissions.html index eaf4289..c9658e2 100644 --- a/docs/permissions.html +++ b/docs/permissions.html @@ -263,7 +263,7 @@

- Taxonomy Metadata + Taxonomy

@@ -360,7 +360,7 @@

- Taxonomy + Taxonomy Metadata

diff --git a/package-lock.json b/package-lock.json index d5ef323..094eca7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,6 +111,16 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, + "@topcoder-platform/topcoder-bus-api-wrapper": { + "version": "github:topcoder-platform/tc-bus-api-wrapper#f8cbd335a0e0b4d6edd7cae859473593271fd97f", + "from": "github:topcoder-platform/tc-bus-api-wrapper", + "requires": { + "joi": "^13.4.0", + "lodash": "^4.17.15", + "superagent": "^3.8.3", + "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4" + } + }, "@types/body-parser": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", @@ -298,6 +308,144 @@ } } }, + "ansi-bgblack": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgblack/-/ansi-bgblack-0.1.1.tgz", + "integrity": "sha1-poulAHiHcBtqr74/oNrf36juPKI=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgblue": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgblue/-/ansi-bgblue-0.1.1.tgz", + "integrity": "sha1-Z73ATtybm1J4lp2hlt6j11yMNhM=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgcyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgcyan/-/ansi-bgcyan-0.1.1.tgz", + "integrity": "sha1-WEiUJWAL3p9VBwaN2Wnr/bUP52g=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bggreen": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bggreen/-/ansi-bggreen-0.1.1.tgz", + "integrity": "sha1-TjGRJIUplD9DIelr8THRwTgWr0k=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgmagenta": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgmagenta/-/ansi-bgmagenta-0.1.1.tgz", + "integrity": "sha1-myhDLAduqpmUGGcqPvvhk5HCx6E=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgred": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgred/-/ansi-bgred-0.1.1.tgz", + "integrity": "sha1-p2+Sg4OCukMpCmwXeEJPmE1vEEE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgwhite": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgwhite/-/ansi-bgwhite-0.1.1.tgz", + "integrity": "sha1-ZQRlE3elim7OzQMxmU5IAljhG6g=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bgyellow": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bgyellow/-/ansi-bgyellow-0.1.1.tgz", + "integrity": "sha1-w/4usIzUdmSAKeaHTRWgs49h1E8=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-black": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-black/-/ansi-black-0.1.1.tgz", + "integrity": "sha1-9hheiJNgslRaHsUMC/Bj/EMDJFM=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-blue": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-blue/-/ansi-blue-0.1.1.tgz", + "integrity": "sha1-FbgEmQ6S/JyoxUds6PaZd3wh7b8=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-bold": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-bold/-/ansi-bold-0.1.1.tgz", + "integrity": "sha1-PmOVCvWswq4uZw5vZ96xFdGl9QU=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-colors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-0.2.0.tgz", + "integrity": "sha1-csMd4qDZoszQysMMyYI+6y9kNLU=", + "requires": { + "ansi-bgblack": "^0.1.1", + "ansi-bgblue": "^0.1.1", + "ansi-bgcyan": "^0.1.1", + "ansi-bggreen": "^0.1.1", + "ansi-bgmagenta": "^0.1.1", + "ansi-bgred": "^0.1.1", + "ansi-bgwhite": "^0.1.1", + "ansi-bgyellow": "^0.1.1", + "ansi-black": "^0.1.1", + "ansi-blue": "^0.1.1", + "ansi-bold": "^0.1.1", + "ansi-cyan": "^0.1.1", + "ansi-dim": "^0.1.1", + "ansi-gray": "^0.1.1", + "ansi-green": "^0.1.1", + "ansi-grey": "^0.1.1", + "ansi-hidden": "^0.1.1", + "ansi-inverse": "^0.1.1", + "ansi-italic": "^0.1.1", + "ansi-magenta": "^0.1.1", + "ansi-red": "^0.1.1", + "ansi-reset": "^0.1.1", + "ansi-strikethrough": "^0.1.1", + "ansi-underline": "^0.1.1", + "ansi-white": "^0.1.1", + "ansi-yellow": "^0.1.1", + "lazy-cache": "^2.0.1" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-dim": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-dim/-/ansi-dim-0.1.1.tgz", + "integrity": "sha1-QN5MYDqoCG2Oeoa4/5mNXDbu/Ww=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -315,11 +463,91 @@ } } }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-green": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-green/-/ansi-green-0.1.1.tgz", + "integrity": "sha1-il2al55FjVfEDjNYCzc5C44Q0Pc=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-grey": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-grey/-/ansi-grey-0.1.1.tgz", + "integrity": "sha1-WdmLasK6GfilF5jphT+6eDOaM8E=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-hidden": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-hidden/-/ansi-hidden-0.1.1.tgz", + "integrity": "sha1-7WpMSY0rt8uyidvyqNHcyFZ/rg8=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-inverse": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-inverse/-/ansi-inverse-0.1.1.tgz", + "integrity": "sha1-tq9Fgm/oJr+1KKbHmIV5Q1XM0mk=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-italic": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-italic/-/ansi-italic-0.1.1.tgz", + "integrity": "sha1-EEdDRj9iXBQqA2c5z4XtpoiYbyM=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-magenta": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-magenta/-/ansi-magenta-0.1.1.tgz", + "integrity": "sha1-BjtboW+z8j4c/aKwfAqJ3hHkMK4=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, + "ansi-reset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-reset/-/ansi-reset-0.1.1.tgz", + "integrity": "sha1-5+cSksPH3c1NYu9KbHwFmAkRw7c=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-strikethrough": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-strikethrough/-/ansi-strikethrough-0.1.1.tgz", + "integrity": "sha1-2Eh3FAss/wfRyT685pkE9oiF5Wg=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -329,6 +557,35 @@ "color-convert": "^1.9.0" } }, + "ansi-underline": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-underline/-/ansi-underline-0.1.1.tgz", + "integrity": "sha1-38kg9Ml7WXfqFi34/7mIMIqqcaQ=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-white": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-white/-/ansi-white-0.1.1.tgz", + "integrity": "sha1-nHe3wZPF7pkuYBHTbsTJIbRXiUQ=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "ansi-yellow": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-yellow/-/ansi-yellow-0.1.1.tgz", + "integrity": "sha1-y5NW8vRscy8OMZnmEClVp32oPB0=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -352,6 +609,29 @@ "sprintf-js": "~1.0.2" } }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-swap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arr-swap/-/arr-swap-1.0.1.tgz", + "integrity": "sha1-FHWQ7WX8gVvAf+8Jl8Llgj1kNTQ=", + "requires": { + "is-number": "^3.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -394,6 +674,14 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, + "async-hook-jl": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", + "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", + "requires": { + "stack-chain": "^1.3.7" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -751,6 +1039,31 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "choices-separator": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/choices-separator/-/choices-separator-2.0.0.tgz", + "integrity": "sha1-kv0XYxgteQM/XFxR0Lo1LlVnxpY=", + "requires": { + "ansi-dim": "^0.1.1", + "debug": "^2.6.6", + "strip-color": "^0.1.0" + }, + "dependencies": { + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -804,6 +1117,24 @@ "wrap-ansi": "^2.0.0" } }, + "clone-deep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-1.0.0.tgz", + "integrity": "sha512-hmJRX8x1QOJVV+GUjOBzi6iauhPqc9hIF6xitWRBbiPZOBb6vGo/mDRIK9P74RTKSQK7AE8B0DDWY/vpRrPmQw==", + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^5.0.0", + "shallow-clone": "^1.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -813,6 +1144,16 @@ "mimic-response": "^1.0.0" } }, + "cls-hooked": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", + "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", + "requires": { + "async-hook-jl": "^1.7.6", + "emitter-listener": "^1.0.1", + "semver": "^5.4.1" + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -833,6 +1174,15 @@ } } }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", @@ -886,6 +1236,11 @@ "delayed-stream": "~1.0.0" } }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -942,6 +1297,16 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, "core-js": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", @@ -1050,6 +1415,14 @@ "object-keys": "^1.0.12" } }, + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, "deglob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/deglob/-/deglob-4.0.1.tgz", @@ -1161,40 +1534,6 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, - "elasticsearch": { - "version": "16.7.2", - "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-16.7.2.tgz", - "integrity": "sha512-1ZLKZlG2ABfYVBX2d7/JgxOsKJrM5Yu62GvshWu7ZSvhxPomCN4Gas90DS51yYI56JolY0XGhyiRlUhLhIL05Q==", - "requires": { - "agentkeepalive": "^3.4.1", - "chalk": "^1.0.0", - "lodash": "^4.17.10" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^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=" - } - } - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1228,6 +1567,11 @@ "is-arrayish": "^0.2.1" } }, + "error-symbol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/error-symbol/-/error-symbol-0.1.0.tgz", + "integrity": "sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y=" + }, "es-abstract": { "version": "1.18.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", @@ -1750,6 +2094,14 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -1933,6 +2285,19 @@ } } }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "requires": { + "for-in": "^1.0.1" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -1948,6 +2313,11 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2156,6 +2526,11 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, + "hoek": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.4.tgz", + "integrity": "sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w==" + }, "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -2287,6 +2662,11 @@ "wrappy": "1" } }, + "info-symbol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/info-symbol/-/info-symbol-0.1.0.tgz", + "integrity": "sha1-J4QdcoZ920JCzWEtecEGM4gcang=" + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -2412,6 +2792,21 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + } + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -2441,6 +2836,11 @@ "call-bind": "^1.0.2" } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "is-callable": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", @@ -2464,12 +2864,49 @@ "has": "^1.0.3" } }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + } + } + }, "is-date-object": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", "dev": true }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2539,6 +2976,14 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, "is-regex": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", @@ -2579,6 +3024,11 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, "is-yarn-global": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", @@ -2590,21 +3040,46 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + + "joi": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-13.7.0.tgz", + "integrity": "sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q==", + "requires": { + "hoek": "5.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + }, + "jmespath": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, "js-tokens": { "version": "4.0.0", @@ -2765,6 +3240,19 @@ "json-buffer": "3.0.0" } }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "koalas": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/koalas/-/koalas-1.0.2.tgz", + "integrity": "sha1-MYQz8HQjXbePrlZhoCqMpT7ilc0=" + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -2779,6 +3267,14 @@ "package-json": "^6.3.0" } }, + "lazy-cache": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", + "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", + "requires": { + "set-getter": "^0.1.0" + } + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -2882,6 +3378,29 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "log-ok": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/log-ok/-/log-ok-0.1.1.tgz", + "integrity": "sha1-vqPdNqzQuKckDXhza1uXxlREozQ=", + "requires": { + "ansi-green": "^0.1.1", + "success-symbol": "^0.1.0" + } + }, + "log-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/log-utils/-/log-utils-0.2.1.tgz", + "integrity": "sha1-pMIXoN2aUFFdm5ICBgkas9TgMc8=", + "requires": { + "ansi-colors": "^0.2.0", + "error-symbol": "^0.1.0", + "info-symbol": "^0.1.0", + "log-ok": "^0.1.1", + "success-symbol": "^0.1.0", + "time-stamp": "^1.0.1", + "warning-symbol": "^0.1.0" + } + }, "logform": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", @@ -2959,6 +3478,14 @@ } } }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3022,6 +3549,22 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=" + } + } + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -3175,6 +3718,59 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + } + } + }, "object-inspect": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", @@ -3187,6 +3783,14 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, "object.assign": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", @@ -3637,6 +4241,11 @@ } } }, + "pointer-symbol": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pointer-symbol/-/pointer-symbol-1.0.0.tgz", + "integrity": "sha1-YPkRAgTqepKbYmRKITFVQ8uz1Ec=" + }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -3688,6 +4297,155 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "prompt-actions": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/prompt-actions/-/prompt-actions-3.0.2.tgz", + "integrity": "sha512-dhz2Fl7vK+LPpmnQ/S/eSut4BnH4NZDLyddHKi5uTU/2PDn3grEMGkgsll16V5RpVUh/yxdiam0xsM0RD4xvtg==", + "requires": { + "debug": "^2.6.8" + }, + "dependencies": { + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "prompt-base": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/prompt-base/-/prompt-base-4.1.0.tgz", + "integrity": "sha512-svGzgLUKZoqomz9SGMkf1hBG8Wl3K7JGuRCXc/Pv7xw8239hhaTBXrmjt7EXA9P/QZzdyT8uNWt9F/iJTXq75g==", + "requires": { + "component-emitter": "^1.2.1", + "debug": "^3.0.1", + "koalas": "^1.0.2", + "log-utils": "^0.2.1", + "prompt-actions": "^3.0.2", + "prompt-question": "^5.0.1", + "readline-ui": "^2.2.3", + "readline-utils": "^2.2.3", + "static-extend": "^0.1.2" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "prompt-choices": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/prompt-choices/-/prompt-choices-4.1.0.tgz", + "integrity": "sha512-ZNYLv6rW9z9n0WdwCkEuS+w5nUAGzRgtRt6GQ5aFNFz6MIcU7nHFlHOwZtzy7RQBk80KzUGPSRQphvMiQzB8pg==", + "requires": { + "arr-flatten": "^1.1.0", + "arr-swap": "^1.0.1", + "choices-separator": "^2.0.0", + "clone-deep": "^4.0.0", + "collection-visit": "^1.0.0", + "define-property": "^2.0.2", + "is-number": "^6.0.0", + "kind-of": "^6.0.2", + "koalas": "^1.0.2", + "log-utils": "^0.2.1", + "pointer-symbol": "^1.0.0", + "radio-symbol": "^2.0.0", + "set-value": "^3.0.0", + "strip-color": "^0.1.0", + "terminal-paginator": "^2.0.2", + "toggle-array": "^1.0.1" + }, + "dependencies": { + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "is-number": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-6.0.0.tgz", + "integrity": "sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==" + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "requires": { + "kind-of": "^6.0.2" + } + } + } + }, + "prompt-confirm": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/prompt-confirm/-/prompt-confirm-2.0.4.tgz", + "integrity": "sha512-X5lzbC8/kMNHdPOqQPfMKpH4VV2f7v2OTRJoN69ZYBirSwTeQaf9ZhmzPEO9ybMA0YV2Pha5MV27u2/U4ahWfg==", + "requires": { + "ansi-cyan": "^0.1.1", + "prompt-base": "^4.0.1" + } + }, + "prompt-question": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/prompt-question/-/prompt-question-5.0.2.tgz", + "integrity": "sha512-wreaLbbu8f5+7zXds199uiT11Ojp59Z4iBi6hONlSLtsKGTvL2UY8VglcxQ3t/X4qWIxsNCg6aT4O8keO65v6Q==", + "requires": { + "clone-deep": "^1.0.0", + "debug": "^3.0.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "kind-of": "^5.0.2", + "koalas": "^1.0.2", + "prompt-choices": "^4.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, "prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", @@ -3794,6 +4552,16 @@ } } }, + "radio-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/radio-symbol/-/radio-symbol-2.0.0.tgz", + "integrity": "sha1-eqm/xQSFY21S3XbWqOYxspB5muE=", + "requires": { + "ansi-gray": "^0.1.1", + "ansi-green": "^0.1.1", + "is-windows": "^1.0.1" + } + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -3874,6 +4642,99 @@ "picomatch": "^2.2.1" } }, + "readline-ui": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/readline-ui/-/readline-ui-2.2.3.tgz", + "integrity": "sha512-ix7jz0PxqQqcIuq3yQTHv1TOhlD2IHO74aNO+lSuXsRYm1d+pdyup1yF3zKyLK1wWZrVNGjkzw5tUegO2IDy+A==", + "requires": { + "component-emitter": "^1.2.1", + "debug": "^2.6.8", + "readline-utils": "^2.2.1", + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "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" + } + }, + "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=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "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=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "readline-utils": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/readline-utils/-/readline-utils-2.2.3.tgz", + "integrity": "sha1-b4R9a48ZFcORtYHDZ81HhzhiNRo=", + "requires": { + "arr-flatten": "^1.1.0", + "extend-shallow": "^2.0.1", + "is-buffer": "^1.1.5", + "is-number": "^3.0.0", + "is-windows": "^1.0.1", + "koalas": "^1.0.2", + "mute-stream": "0.0.7", + "strip-color": "^0.1.0", + "window-size": "^1.1.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "window-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-1.1.1.tgz", + "integrity": "sha512-5D/9vujkmVQ7pSmc0SCBmHXbkv6eaHwXEx65MywhmUMsI8sGqJ972APq1lotfcwMKPFLuCFfL8xGHLIp7jaBmA==", + "requires": { + "define-property": "^1.0.0", + "is-number": "^3.0.0" + } + } + } + }, "reconnect-core": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/reconnect-core/-/reconnect-core-1.3.0.tgz", @@ -4171,11 +5032,44 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "set-getter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.1.tgz", + "integrity": "sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==", + "requires": { + "to-object-path": "^0.3.0" + } + }, + "set-value": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-3.0.2.tgz", + "integrity": "sha512-npjkVoz+ank0zjlV9F47Fdbjfj/PfXyVhZvGALWsyIYU/qrMzpi6avjKW3/7KeSU2Df3I46BrN1xOI1+6vW0hA==", + "requires": { + "is-plain-object": "^2.0.4" + } + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -4191,6 +5085,11 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -4293,6 +5192,11 @@ "tweetnacl": "~0.14.0" } }, + "stack-chain": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", + "integrity": "sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=" + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -4327,6 +5231,76 @@ "pkg-conf": "^3.1.0" } }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -4393,12 +5367,71 @@ "is-utf8": "^0.2.0" } }, + "strip-color": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/strip-color/-/strip-color-0.1.0.tgz", + "integrity": "sha1-EG9l09PmotlAHKwOsM6LinArT3s=" + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "success-symbol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz", + "integrity": "sha1-JAIuSG878c3KCUKDt2nEctO3KJc=" + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -4493,6 +5526,31 @@ "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", "dev": true }, + "terminal-paginator": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/terminal-paginator/-/terminal-paginator-2.0.2.tgz", + "integrity": "sha512-IZMT5ECF9p4s+sNCV8uvZSW9E1+9zy9Ji9xz2oee8Jfo7hUFpauyjxkhfRcIH6Lu3Wdepv5D1kVRc8Hx74/LfQ==", + "requires": { + "debug": "^2.6.6", + "extend-shallow": "^2.0.1", + "log-utils": "^0.2.1" + }, + "dependencies": { + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -4510,6 +5568,11 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -4519,6 +5582,14 @@ "os-tmpdir": "~1.0.2" } }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + } + }, "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -4534,11 +5605,34 @@ "is-number": "^7.0.0" } }, + "toggle-array": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toggle-array/-/toggle-array-1.0.1.tgz", + "integrity": "sha1-y/WEB5K9UJfzMReugkyTKv/ofVg=", + "requires": { + "isobject": "^3.0.0" + } + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + }, + "dependencies": { + "hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" + } + } + }, "toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", @@ -4846,6 +5940,11 @@ "extsprintf": "^1.2.0" } }, + "warning-symbol": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/warning-symbol/-/warning-symbol-0.1.0.tgz", + "integrity": "sha1-uzHdEbeg+dZ6su2V9Fe2WCW7rSE=" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 9f15270..1e662fd 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "migrate-db-to-es": "node scripts/db/dumpDbToEs.js", "migrations": "node scripts/db/migrations.js", "insert-data": "node scripts/db/insert-data.js", + "create-index": "node scripts/es/createIndex.js", + "delete-index": "node scripts/es/deleteIndex.js", "generate:doc:permissions": "node scripts/permissions-doc", "generate:doc:permissions:dev": "npx nodemon --watch scripts/permissions-doc --watch src --ext js,jsx,hbs --exec npm run generate:doc:permissions" }, @@ -23,7 +25,9 @@ "dependencies": { "@elastic/elasticsearch": "^7.9.1", "@hapi/joi": "^16.1.8", + "@topcoder-platform/topcoder-bus-api-wrapper": "github:topcoder-platform/tc-bus-api-wrapper", "body-parser": "^1.19.0", + "cls-hooked": "^4.2.2", "config": "^3.2.4", "cors": "^2.8.5", "express": "^4.17.1", @@ -32,6 +36,7 @@ "js-yaml": "^3.13.1", "lodash": "^4.17.19", "pgtools": "^0.3.0", + "prompt-confirm": "^2.0.4", "sequelize": "^6.3.5", "swagger-ui-express": "^4.1.4", "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4", diff --git a/scripts/constants.js b/scripts/constants.js index fa5fb4d..b9dd210 100644 --- a/scripts/constants.js +++ b/scripts/constants.js @@ -8,35 +8,13 @@ const config = require('config') const topResources = { taxonomy: { index: config.get('ES.DOCUMENTS.taxonomy.index'), - type: config.get('ES.DOCUMENTS.taxonomy.type'), - enrich: { - policyName: config.get('ES.DOCUMENTS.taxonomy.enrichPolicyName'), - matchField: 'id', - enrichFields: ['id', 'name', 'created', 'updated', 'createdBy', 'updatedBy'] - }, - pipeline: { - id: config.get('ES.DOCUMENTS.taxonomy.pipelineId'), - field: 'taxonomyId', - targetField: 'taxonomy', - maxMatches: '1' - } + type: config.get('ES.DOCUMENTS.taxonomy.type') }, skill: { index: config.get('ES.DOCUMENTS.skill.index'), - type: config.get('ES.DOCUMENTS.skill.type'), - enrich: { - policyName: config.get('ES.DOCUMENTS.skill.enrichPolicyName'), - matchField: 'id', - enrichFields: ['id', 'taxonomyId', 'name', 'externalId', 'uri', 'created', 'updated', 'createdBy', 'updatedBy', 'taxonomyName'] - }, - ingest: { - pipeline: { - id: config.get('ES.DOCUMENTS.taxonomy.pipelineId') - } - } + type: config.get('ES.DOCUMENTS.skill.type') } - } const modelToESIndexMapping = { @@ -44,7 +22,72 @@ const modelToESIndexMapping = { Skill: 'skill' } +// The es index property mapping +const esIndexPropertyMapping = { + [topResources.skill.index]: { + created: { + type: 'date' + }, + createdBy: { + type: 'keyword' + }, + externalId: { + type: 'keyword' + }, + id: { + type: 'keyword' + }, + metadata: { + type: 'object', + enabled: 'false' + }, + name: { + type: 'keyword' + }, + taxonomyId: { + type: 'keyword' + }, + taxonomyName: { + type: 'keyword' + }, + updated: { + type: 'date' + }, + updatedBy: { + type: 'keyword' + }, + uri: { + type: 'text' + } + }, + [topResources.taxonomy.index]: { + created: { + type: 'date' + }, + createdBy: { + type: 'keyword' + }, + id: { + type: 'keyword' + }, + metadata: { + type: 'object', + enabled: 'false' + }, + name: { + type: 'keyword' + }, + updated: { + type: 'date' + }, + updatedBy: { + type: 'keyword' + } + } +} + module.exports = { topResources, - modelToESIndexMapping + modelToESIndexMapping, + esIndexPropertyMapping } diff --git a/scripts/db/dropAll.js b/scripts/db/dropAll.js index be168ac..23725e2 100644 --- a/scripts/db/dropAll.js +++ b/scripts/db/dropAll.js @@ -13,18 +13,6 @@ const { getESClient } = require('../../src/common/es-client') async function main () { const client = getESClient() - // delete es pipelines - try { - logger.info('Deleting all pipelines...') - await client.ingest.deletePipeline({ - id: topResources.taxonomy.pipeline.id - }) - logger.info('Successfully deleted') - } catch (e) { - console.error(e) - logger.warn('Delete all ingest pipelines failed') - } - // delete data in es const keys = Object.keys(sequelize.models) for (let i = 0; i < keys.length; i++) { @@ -32,18 +20,11 @@ async function main () { const esResourceName = modelToESIndexMapping[key] try { if (_.includes(_.keys(topResources), esResourceName)) { - if (topResources[esResourceName].enrich) { - logger.info(`Deleting enrich policy for ${esResourceName}`) - await client.enrich.deletePolicy({ - name: topResources[esResourceName].enrich.policyName - }) - logger.info(`Successfully deleted enrich policy for ${esResourceName}`) - } logger.info(`Deleting index for ${esResourceName}`) await client.indices.delete({ index: topResources[esResourceName].index }) - logger.info(`Successfully deleted enrich policy for ${esResourceName}`) + logger.info(`Successfully deleted index for ${esResourceName}`) } } catch (e) { console.error(e) @@ -54,6 +35,10 @@ async function main () { // delete tables try { await sequelize.drop() + // the dropped tables cannot be re-created via command `npm run migrations up` + // without dropping the SequelizeMeta table first + // so here we drop the SequelizeMeta table too. + await sequelize.query('DROP TABLE IF EXISTS "SequelizeMeta";') } catch (e) { console.error(e) logger.warn('deleting tables failed') diff --git a/scripts/db/dumpDbToEs.js b/scripts/db/dumpDbToEs.js index 648a6aa..9c1bf9d 100644 --- a/scripts/db/dumpDbToEs.js +++ b/scripts/db/dumpDbToEs.js @@ -12,15 +12,8 @@ const { const models = sequelize.models -// Declares the ordering of the resource data insertion, to ensure that enrichment happens correctly -const RESOURCES_IN_ORDER = [ - 'taxonomy', - 'skill' -] - const client = getESClient() -const RESOURCE_NOT_FOUND = 'resource_not_found_exception' const INDEX_NOT_FOUND = 'index_not_found_exception' /** @@ -29,17 +22,6 @@ const INDEX_NOT_FOUND = 'index_not_found_exception' */ async function cleanupES (keys) { const client = getESClient() - try { - await client.ingest.deletePipeline({ - id: topResources.taxonomy.pipeline.id - }) - } catch (e) { - if (e.meta && e.meta.body.error.type === RESOURCE_NOT_FOUND) { - // Ignore - } else { - throw e - } - } try { for (let i = 0; i < keys.length; i++) { @@ -47,20 +29,6 @@ async function cleanupES (keys) { if (models[key].tableName) { const esResourceName = modelToESIndexMapping[key] if (_.includes(_.keys(topResources), esResourceName)) { - if (topResources[esResourceName].enrich) { - try { - await client.enrich.deletePolicy({ - name: topResources[esResourceName].enrich.policyName - }) - } catch (e) { - if (e.meta && e.meta.body.error.type === RESOURCE_NOT_FOUND) { - // Ignore - } else { - throw e - } - } - } - try { await client.indices.delete({ index: topResources[esResourceName].index @@ -98,7 +66,6 @@ async function insertBulkIntoES (esResourceName, dataset) { index: resourceConfig.index, type: resourceConfig.type, body, - pipeline: resourceConfig.ingest ? resourceConfig.ingest.pipeline.id : undefined, refresh: 'wait_for' }) } catch (e) { @@ -108,99 +75,12 @@ async function insertBulkIntoES (esResourceName, dataset) { } } -/** - * Creates and executes the enrich policy for the provided model - * @param {String} modelName The model name - */ -async function createAndExecuteEnrichPolicy (modelName) { - const esResourceName = modelToESIndexMapping[modelName] - - if (_.includes(_.keys(topResources), esResourceName) && topResources[esResourceName].enrich) { - await client.enrich.putPolicy({ - name: topResources[esResourceName].enrich.policyName, - body: { - match: { - indices: topResources[esResourceName].index, - match_field: topResources[esResourceName].enrich.matchField, - enrich_fields: topResources[esResourceName].enrich.enrichFields - } - } - }) - await client.enrich.executePolicy({ name: topResources[esResourceName].enrich.policyName }) - } -} - -/** - * Creates the ingest pipeline using the enrich policy - * @param {String} modelName The model name - */ -async function createEnrichProcessor (modelName) { - const esResourceName = modelToESIndexMapping[modelName] - - if (_.includes(_.keys(topResources), esResourceName) && topResources[esResourceName].pipeline) { - if (topResources[esResourceName].pipeline.processors) { - const processors = [] - - for (let i = 0; i < topResources[esResourceName].pipeline.processors.length; i++) { - const ep = topResources[esResourceName].pipeline.processors[i] - processors.push({ - foreach: { - field: ep.referenceField, - ignore_missing: true, - processor: { - enrich: { - policy_name: ep.enrichPolicyName, - ignore_missing: true, - field: ep.field, - target_field: ep.targetField, - max_matches: ep.maxMatches - } - } - } - }) - } - - await client.ingest.putPipeline({ - id: topResources[esResourceName].pipeline.id, - body: { - processors - } - }) - } else { - await client.ingest.putPipeline({ - id: topResources[esResourceName].pipeline.id, - body: { - processors: [{ - enrich: { - policy_name: topResources[esResourceName].enrich.policyName, - field: topResources[esResourceName].pipeline.field, - target_field: topResources[esResourceName].pipeline.targetField, - max_matches: topResources[esResourceName].pipeline.maxMatches - } - }] - } - }) - } - } -} - /** * import test data * @return {Promise} */ async function main () { - let keys = Object.keys(models) - - // Sort the models in the order of insertion (for correct enrichment) - const temp = Array(keys.length).fill(null) - keys.forEach(k => { - if (sequelize.models[k].name) { - const esResourceName = modelToESIndexMapping[k] - const index = RESOURCES_IN_ORDER.indexOf(esResourceName) - temp[index] = k - } - }) - keys = _.compact(temp) + const keys = Object.keys(models) await cleanupES(keys) @@ -241,21 +121,6 @@ async function main () { logger.warn('import data for ' + key + ' failed') continue } - try { - await createAndExecuteEnrichPolicy(key) - logger.info('create and execute enrich policy for ' + key + ' done') - } catch (e) { - logger.error(JSON.stringify(_.get(e, 'meta.body', ''), null, 4)) - logger.warn('create and execute enrich policy for ' + key + ' failed') - } - - try { - await createEnrichProcessor(key) - logger.info('create enrich processor (pipeline) for ' + key + ' done') - } catch (e) { - logger.error(JSON.stringify(_.get(e, 'meta.body', ''), null, 4)) - logger.warn('create enrich processor (pipeline) for ' + key + ' failed') - } } logger.info('all done') process.exit(0) diff --git a/scripts/es/createIndex.js b/scripts/es/createIndex.js new file mode 100644 index 0000000..d14431d --- /dev/null +++ b/scripts/es/createIndex.js @@ -0,0 +1,25 @@ +/** + * Create index in Elasticsearch + */ +const logger = require('../../src/common/logger') +const scriptHelper = require('../../src/common/script-helper') +const constants = require('../constants') + +const indices = Object.values(constants.topResources).map(x => x.index) +const userPrompt = `WARNING: Are you sure want to create the following elasticsearch indices: ${indices}?` + +async function createIndex () { + await scriptHelper.promptUser(userPrompt, async () => { + for (const index of indices) { + try { + await scriptHelper.createIndex(index, logger) + } catch (err) { + logger.logFullError(err) + process.exit(1) + } + } + process.exit(0) + }) +} + +createIndex() diff --git a/scripts/es/deleteIndex.js b/scripts/es/deleteIndex.js new file mode 100644 index 0000000..4f239c0 --- /dev/null +++ b/scripts/es/deleteIndex.js @@ -0,0 +1,25 @@ +/** + * Delete index in Elasticsearch + */ +const logger = require('../../src/common/logger') +const scriptHelper = require('../../src/common/script-helper') +const constants = require('../constants') + +const indices = Object.values(constants.topResources).map(x => x.index) +const userPrompt = `WARNING: this would remove existent data! Are you sure want to delete the following eleasticsearch indices: ${indices}?` + +async function deleteIndex () { + await scriptHelper.promptUser(userPrompt, async () => { + for (const index of indices) { + try { + await scriptHelper.deleteIndex(index, logger) + } catch (err) { + logger.logFullError(err) + process.exit(1) + } + } + process.exit(0) + }) +} + +deleteIndex() diff --git a/src/bootstrap.js b/src/bootstrap.js index 087ba72..5eca77b 100755 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -9,6 +9,7 @@ const logger = require('./common/logger') const joi = require('@hapi/joi') joi.id = () => joi.number().integer().min(1) +joi.page = () => joi.number().integer().min(1) joi.pageSize = () => joi.number().integer().min(1).max(config.get('MAX_PAGE_SIZE')) joi.prominence = (name) => joi.string().custom((value, helper) => { // check if value is in the range [0, 1] diff --git a/src/common/errors.js b/src/common/errors.js index 5a3a868..0684425 100644 --- a/src/common/errors.js +++ b/src/common/errors.js @@ -24,6 +24,5 @@ module.exports = { newAuthError: msg => new AppError(401, msg || 'Auth failed.'), newPermissionError: msg => new AppError(403, msg || 'The entity does not exist.'), newConflictError: msg => new AppError(409, msg || 'The entity does not exist.'), - deleteConflictError: msg => new AppError(400, msg || 'Please delete child records first'), - elasticSearchEnrichError: msg => new AppError(500, msg || 'Elasticsearch enrich failed') + deleteConflictError: msg => new AppError(400, msg || 'Please delete child records first') } diff --git a/src/common/es-helper.js b/src/common/es-helper.js index c250010..f1083f3 100644 --- a/src/common/es-helper.js +++ b/src/common/es-helper.js @@ -65,6 +65,8 @@ async function insertIntoES (esResourceName, data) { /** * updated ES record + * if the metadata field is provided existent metadata will be entirely replaced with the new value. + * * @param esResourceName the ES resource name * @param data the data to update */ @@ -75,7 +77,16 @@ async function updateESRecord (esResourceName, data) { type: resourceConfig.type, refresh: config.get('ES.ES_REFRESH'), id: data.id, - body: { + body: data.metadata ? { + script: { + lang: 'painless', + source: 'ctx._source = params.data; ctx._source.metadata = params.metadata', + params: { + data: _.omit(data, ['metadata']), + metadata: data.metadata + } + } + } : { doc: data } }) @@ -303,7 +314,7 @@ async function searchElasticSearch (resource, ...args) { const preResFilters = parseResourceFilter(resource, params, false) const preResFilterResults = [] // resolve pre resource filters - if (!params.enrich && preResFilters.length > 0) { + if (preResFilters.length > 0) { for (const filter of preResFilters) { const resolved = await resolveResFilter(filter, resource) preResFilterResults.push(resolved) @@ -333,7 +344,7 @@ async function searchElasticSearch (resource, ...args) { } // set pre res filter results - if (!params.enrich && preResFilterResults.length > 0) { + if (preResFilterResults.length > 0) { for (const filter of preResFilterResults) { const matchField = `${filter.queryField}` setFilterValueToEsQuery(esQuery, matchField, filter.value, filter.queryField) @@ -342,7 +353,7 @@ async function searchElasticSearch (resource, ...args) { const ownResFilters = parseResourceFilter(resource, params, true) // set it's own res filter to the main query - if (!params.enrich && ownResFilters.length > 0) { + if (ownResFilters.length > 0) { setResourceFilterToEsQuery(ownResFilters, esQuery) } diff --git a/src/common/helper.js b/src/common/helper.js index c197ee9..0d5cb97 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -1,6 +1,11 @@ const querystring = require('querystring') const _ = require('lodash') const { getControllerMethods } = require('./controller-helper') +const logger = require('./logger') +const config = require('config') +const busApi = require('@topcoder-platform/topcoder-bus-api-wrapper') +const busApiClient = busApi(_.pick(config, ['AUTH0_URL', 'AUTH0_AUDIENCE', 'TOKEN_CACHE_TIME', 'AUTH0_CLIENT_ID', + 'AUTH0_CLIENT_SECRET', 'BUSAPI_URL', 'KAFKA_ERROR_TOPIC', 'AUTH0_PROXY_SERVER_URL'])) /** * get auth user handle or id @@ -90,9 +95,29 @@ function omitAuditFields (entity) { } } +/** + * Send error event to Kafka + * @params {String} topic the topic name + * @params {Object} payload the payload + * @params {String} action for which operation error occurred + */ +async function publishError (topic, payload, action) { + _.set(payload, 'apiAction', action) + const message = { + topic, + originator: config.KAFKA_MESSAGE_ORIGINATOR, + timestamp: new Date().toISOString(), + 'mime-type': 'application/json', + payload + } + logger.debug(`Publish error to Kafka topic ${topic}, ${JSON.stringify(message, null, 2)}`) + await busApiClient.postEvent(message) +} + module.exports = { getAuthUser, injectSearchMeta, getControllerMethods, - omitAuditFields + omitAuditFields, + publishError } diff --git a/src/common/script-helper.js b/src/common/script-helper.js new file mode 100644 index 0000000..dee1fc8 --- /dev/null +++ b/src/common/script-helper.js @@ -0,0 +1,72 @@ +const Confirm = require('prompt-confirm') +const { getESClient } = require('../common/es-client') +const { esIndexPropertyMapping } = require('../../scripts/constants') + +const esClient = getESClient() + +/** + * Prompt the user with a y/n query and call a callback function based on the answer + * @param {string} promptQuery the query to ask the user + * @param {function} cb the callback function + */ +async function promptUser (promptQuery, cb) { + if (process.argv.includes('--force')) { + await cb() + return + } + + const prompt = new Confirm(promptQuery) + prompt.ask(async (answer) => { + if (answer) { + await cb() + } + }) +} + +/** + * Create index in elasticsearch + * @param {String} index the index name + * @param {Object} logger the logger object + */ +async function createIndex (index, logger) { + await esClient.indices.create({ index }) + await esClient.indices.close({ index }) + await esClient.indices.putSettings({ + index: index, + body: { + settings: { + analysis: { + normalizer: { + lowercaseNormalizer: { + filter: ['lowercase'] + } + } + } + } + } + }) + await esClient.indices.open({ index }) + await esClient.indices.putMapping({ + index, + body: { + properties: esIndexPropertyMapping[index] + } + }) + logger.info(`ES Index ${index} creation succeeded!`) +} + +/** + * Delete index in elasticsearch + * @param {String} index the index name + * @param {Object} logger the logger object + */ +async function deleteIndex (index, logger) { + await esClient.indices.delete({ index }) + logger.info(`ES Index ${index} deletion succeeded!`) +} + +module.exports = { + promptUser, + createIndex, + deleteIndex +} diff --git a/src/common/service-helper.js b/src/common/service-helper.js index 0869d8a..69e7aec 100644 --- a/src/common/service-helper.js +++ b/src/common/service-helper.js @@ -19,6 +19,7 @@ async function createRecordInEs (resource, entity) { await esHelper.insertIntoES(resource, entity) } catch (err) { logger.logFullError(err) + throw err } } @@ -32,6 +33,7 @@ async function patchRecordInEs (resource, entity) { await esHelper.updateESRecord(resource, entity) } catch (err) { logger.logFullError(err) + throw err } } @@ -46,6 +48,7 @@ async function deleteRecordFromEs (id, params, resource) { await esHelper.deleteESRecord(resource, id) } catch (err) { logger.logFullError(err) + throw err } } @@ -61,9 +64,9 @@ async function getRecordInEs (resource, id, params) { const result = await esHelper.getFromElasticSearch(resource, id, params) return result } catch (err) { - // return error if enrich fails or permission fails + // return error if permission fails if (err.status && err.status === 403) { - throw errors.elasticSearchEnrichError(err.message) + throw errors.ForbiddenError(err.message) } logger.logFullError(err) } diff --git a/src/constants.js b/src/constants.js index c347926..12929e0 100644 --- a/src/constants.js +++ b/src/constants.js @@ -43,7 +43,26 @@ const M2M_SCOPES = { } } +const SequelizeCLSNamespace = 'skills-api' + +const API_ACTION = { + SkillCreate: 'skill.create', + SkillUpdate: 'skill.update', + SkillDelete: 'skill.delete', + SkillPutMetadata: 'skill.putMetadata', + SkillPatchMetadata: 'skill.patchMetadata', + SkillDeleteMetadata: 'skill.deleteMetadata', + TaxonomyCreate: 'taxonomy.create', + TaxonomyUpdate: 'taxonomy.update', + TaxonomyDelete: 'taxonomy.delete', + TaxonomyPutMetadata: 'taxonomy.putMetadata', + TaxonomyPatchMetadata: 'taxonomy.patchMetadata', + TaxonomyDeleteMetadata: 'taxonomy.deleteMetadata' +} + module.exports = { MANAGER_ROLES, - M2M_SCOPES + M2M_SCOPES, + SequelizeCLSNamespace, + API_ACTION } diff --git a/src/models/index.js b/src/models/index.js index 4bf6b21..a053cd5 100755 --- a/src/models/index.js +++ b/src/models/index.js @@ -2,11 +2,21 @@ * the model index */ const { Sequelize } = require('sequelize') +const cls = require('cls-hooked') const config = require('config') +const constants = require('../constants') const fs = require('fs') const path = require('path') const logger = require('../common/logger') +// Enable CLS so that when using a managed transaction the transaction will be +// automatically passed to all queries within a callback chain. +// No longer need to pass the transaction manually. +// +// See https://sequelize.org/master/manual/transactions.html for more info +const namespace = cls.createNamespace(constants.SequelizeCLSNamespace) +Sequelize.useCLS(namespace) + /** * the database instance */ diff --git a/src/modules/SkillMetadata/service.js b/src/modules/SkillMetadata/service.js index 5c9e6a9..474d40f 100644 --- a/src/modules/SkillMetadata/service.js +++ b/src/modules/SkillMetadata/service.js @@ -4,11 +4,13 @@ const joi = require('@hapi/joi') const _ = require('lodash') +const config = require('config') const errors = require('../../common/errors') const helper = require('../../common/helper') const dbHelper = require('../../common/db-helper') const serviceHelper = require('../../common/service-helper') +const constants = require('../../constants') const { PERMISSION } = require('../../permissions/constants') const sequelize = require('../../models/index') @@ -21,17 +23,31 @@ const resource = serviceHelper.getResource('Skill') * @param instance the skill instance * @param metadata the new metadata * @param auth the auth object + * @param action for which operation performed * @return the updated skill */ -async function updateMetaData (instance, metadata, auth) { - const newEntity = await instance.update({ - ...instance.dataValues, - updatedBy: helper.getAuthUser(auth), - metadata - }) - const taxonomy = await dbHelper.get(Taxonomy, newEntity.taxonomyId) - await serviceHelper.patchRecordInEs(resource, { ...newEntity.dataValues, taxonomyName: taxonomy.name }) - return helper.omitAuditFields(newEntity.dataValues) +async function updateMetaData (instance, metadata, auth, action) { + let payload + try { + return await sequelize.transaction(async () => { + const newEntity = await instance.update({ + ...instance.dataValues, + updatedBy: helper.getAuthUser(auth), + metadata + }) + + payload = newEntity.dataValues + + const taxonomy = await dbHelper.get(Taxonomy, newEntity.taxonomyId) + await serviceHelper.patchRecordInEs(resource, newEntity.dataValues) + return helper.omitAuditFields({ ...newEntity.dataValues, taxonomyName: taxonomy.name }) + }) + } catch (e) { + if (payload) { + helper.publishError(config.SKILLS_ERROR_TOPIC, payload, action) + } + throw e + } } /** @@ -53,7 +69,7 @@ async function fullyUpdate (id, entity, auth) { serviceHelper.hasPermission(PERMISSION.DELETE_SKILL_METADATA, auth) } - return updateMetaData(instance, entity, auth) + return updateMetaData(instance, entity, auth, constants.API_ACTION.SkillPutMetadata) } fullyUpdate.schema = { @@ -88,7 +104,7 @@ async function particallyUpdate (id, entity, auth) { serviceHelper.hasPermission(PERMISSION.UPDATE_SKILL_METADATA, auth) } - return updateMetaData(instance, { ...instance.dataValues.metadata, ...entity }, auth) + return updateMetaData(instance, { ...instance.dataValues.metadata, ...entity }, auth, constants.API_ACTION.SkillPatchMetadata) } particallyUpdate.schema = { @@ -116,7 +132,7 @@ async function remove (id, fields, auth) { throw errors.NotFoundError(`Metadata fields: ${nonExistingFields} do not exist`) } - return updateMetaData(instance, _.omit(instance.dataValues.metadata, fields), auth) + return updateMetaData(instance, _.omit(instance.dataValues.metadata, fields), auth, constants.API_ACTION.SkillDeleteMetadata) } remove.schema = { diff --git a/src/modules/health/controller.js b/src/modules/health/controller.js index f4641df..98e2bb7 100644 --- a/src/modules/health/controller.js +++ b/src/modules/health/controller.js @@ -2,7 +2,6 @@ * Controller for health check endpoint */ const models = require('../../models') -const config = require('config') const logger = require('../../common/logger') // the topcoder-healthcheck-dropin library returns checksRun count, @@ -30,4 +29,4 @@ async function checkHealth (req, res) { module.exports = { checkHealth -} \ No newline at end of file +} diff --git a/src/modules/skill/service.js b/src/modules/skill/service.js index aa024d7..5b5b541 100644 --- a/src/modules/skill/service.js +++ b/src/modules/skill/service.js @@ -4,11 +4,13 @@ const joi = require('@hapi/joi') const _ = require('lodash') +const config = require('config') const errors = require('../../common/errors') const helper = require('../../common/helper') const dbHelper = require('../../common/db-helper') const serviceHelper = require('../../common/service-helper') +const constants = require('../../constants') const { PERMISSION } = require('../../permissions/constants') const sequelize = require('../../models/index') @@ -32,12 +34,24 @@ async function create (entity, auth) { const taxonomy = await dbHelper.get(Taxonomy, entity.taxonomyId) await dbHelper.makeSureUnique(Skill, entity, uniqueFields) - const result = await dbHelper.create(Skill, entity, auth) - const created = result.dataValues - created.taxonomyName = taxonomy.name - await serviceHelper.createRecordInEs(resource, created) + let payload + try { + return await sequelize.transaction(async () => { + const result = await dbHelper.create(Skill, entity, auth) - return helper.omitAuditFields(created) + payload = result.dataValues + + const created = { ...result.dataValues, taxonomyName: taxonomy.name } + await serviceHelper.createRecordInEs(resource, created) + + return helper.omitAuditFields(created) + }) + } catch (e) { + if (payload) { + helper.publishError(config.SKILLS_ERROR_TOPIC, payload, constants.API_ACTION.SkillCreate) + } + throw e + } } create.schema = { @@ -82,19 +96,31 @@ async function patch (id, entity, auth) { } } - const newEntity = await instance.update({ - ...entity, - updatedBy: helper.getAuthUser(auth) - }) + let payload + try { + return await sequelize.transaction(async () => { + const newEntity = await instance.update({ + ...entity, + updatedBy: helper.getAuthUser(auth) + }) - if (!taxonomy) { - taxonomy = await dbHelper.get(Taxonomy, newEntity.taxonomyId) - } - const updated = newEntity.dataValues - updated.taxonomyName = taxonomy.name - await serviceHelper.patchRecordInEs(resource, updated) + payload = newEntity.dataValues - return helper.omitAuditFields(updated) + if (!taxonomy) { + taxonomy = await dbHelper.get(Taxonomy, newEntity.taxonomyId) + } + const updated = { ...newEntity.dataValues, taxonomyName: taxonomy.name } + + await serviceHelper.patchRecordInEs(resource, updated) + + return helper.omitAuditFields(updated) + }) + } catch (e) { + if (payload) { + helper.publishError(config.SKILLS_ERROR_TOPIC, payload, constants.API_ACTION.SkillUpdate) + } + throw e + } } patch.schema = { @@ -198,7 +224,7 @@ async function search (query) { search.schema = { query: { - page: joi.string().uuid(), + page: joi.page(), perPage: joi.pageSize(), taxonomyId: joi.string().uuid(), name: joi.string(), @@ -215,8 +241,16 @@ search.schema = { * @return no data returned */ async function remove (id, auth, params) { - await dbHelper.remove(Skill, id) - await serviceHelper.deleteRecordFromEs(id, params, resource) + const payload = { id } + try { + return await sequelize.transaction(async () => { + await dbHelper.remove(Skill, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) + }) + } catch (e) { + helper.publishError(config.SKILLS_ERROR_TOPIC, payload, constants.API_ACTION.SkillDelete) + throw e + } } remove.schema = { diff --git a/src/modules/taxonomy/service.js b/src/modules/taxonomy/service.js index f4f06f7..1bea23c 100644 --- a/src/modules/taxonomy/service.js +++ b/src/modules/taxonomy/service.js @@ -4,11 +4,13 @@ const joi = require('@hapi/joi') const _ = require('lodash') +const config = require('config') const errors = require('../../common/errors') const helper = require('../../common/helper') const dbHelper = require('../../common/db-helper') const serviceHelper = require('../../common/service-helper') +const constants = require('../../constants') const { PERMISSION } = require('../../permissions/constants') const sequelize = require('../../models/index') @@ -28,10 +30,22 @@ async function create (entity, auth) { serviceHelper.hasPermission(PERMISSION.ADD_TAXONOMY_METADATA, auth) } - const result = await dbHelper.create(Taxonomy, entity, auth) + let payload + try { + return await sequelize.transaction(async () => { + const result = await dbHelper.create(Taxonomy, entity, auth) - await serviceHelper.createRecordInEs(resource, result.dataValues) - return helper.omitAuditFields(result.dataValues) + payload = result.dataValues + + await serviceHelper.createRecordInEs(resource, result.dataValues) + return helper.omitAuditFields(result.dataValues) + }) + } catch (e) { + if (payload) { + helper.publishError(config.SKILLS_ERROR_TOPIC, payload, constants.API_ACTION.TaxonomyCreate) + } + throw e + } } create.schema = { @@ -64,13 +78,24 @@ async function patch (id, entity, auth) { } } - const newEntity = await instance.update({ - ...entity, - updatedBy: helper.getAuthUser(auth) - }) - - await serviceHelper.patchRecordInEs(resource, newEntity.dataValues) - return helper.omitAuditFields(newEntity.dataValues) + let payload + try { + return await sequelize.transaction(async () => { + const newEntity = await instance.update({ + ...entity, + updatedBy: helper.getAuthUser(auth) + }) + payload = newEntity.dataValues + + await serviceHelper.patchRecordInEs(resource, newEntity.dataValues) + return helper.omitAuditFields(newEntity.dataValues) + }) + } catch (e) { + if (payload) { + helper.publishError(config.SKILLS_ERROR_TOPIC, payload, constants.API_ACTION.TaxonomyUpdate) + } + throw e + } } patch.schema = { @@ -135,7 +160,7 @@ async function search (query) { search.schema = { query: { - page: joi.string().uuid(), + page: joi.page(), perPage: joi.pageSize(), name: joi.string() } @@ -154,8 +179,16 @@ async function remove (id, auth, params) { throw errors.deleteConflictError(`Please delete ${Skill.name} with ids ${existing.map(o => o.id)}`) } - await dbHelper.remove(Taxonomy, id) - await serviceHelper.deleteRecordFromEs(id, params, resource) + const payload = { id } + try { + return await sequelize.transaction(async () => { + await dbHelper.remove(Taxonomy, id) + await serviceHelper.deleteRecordFromEs(id, params, resource) + }) + } catch (e) { + helper.publishError(config.SKILLS_ERROR_TOPIC, payload, constants.API_ACTION.TaxonomyDelete) + throw e + } } remove.schema = { diff --git a/src/modules/taxonomyMetadata/service.js b/src/modules/taxonomyMetadata/service.js index 2395d8d..63eb5c5 100644 --- a/src/modules/taxonomyMetadata/service.js +++ b/src/modules/taxonomyMetadata/service.js @@ -4,11 +4,13 @@ const joi = require('@hapi/joi') const _ = require('lodash') +const config = require('config') const errors = require('../../common/errors') const helper = require('../../common/helper') const dbHelper = require('../../common/db-helper') const serviceHelper = require('../../common/service-helper') +const constants = require('../../constants') const { PERMISSION } = require('../../permissions/constants') const sequelize = require('../../models/index') @@ -20,16 +22,30 @@ const resource = serviceHelper.getResource('Taxonomy') * @param instance the taxonomy instance * @param metadata the new metadata * @param auth the auth object + * @param action for which operation performed * @return the updated taxonomy */ -async function updateMetaData (instance, metadata, auth) { - const newEntity = await instance.update({ - ...instance.dataValues, - updatedBy: helper.getAuthUser(auth), - metadata - }) - await serviceHelper.patchRecordInEs(resource, newEntity.dataValues) - return helper.omitAuditFields(newEntity.dataValues) +async function updateMetaData (instance, metadata, auth, action) { + let payload + try { + return await sequelize.transaction(async () => { + const newEntity = await instance.update({ + ...instance.dataValues, + updatedBy: helper.getAuthUser(auth), + metadata + }) + + payload = newEntity.dataValues + + await serviceHelper.patchRecordInEs(resource, newEntity.dataValues) + return helper.omitAuditFields(newEntity.dataValues) + }) + } catch (e) { + if (payload) { + helper.publishError(config.SKILLS_ERROR_TOPIC, payload, action) + } + throw e + } } /** @@ -51,7 +67,7 @@ async function fullyUpdate (id, entity, auth) { serviceHelper.hasPermission(PERMISSION.DELETE_TAXONOMY_METADATA, auth) } - return updateMetaData(instance, entity, auth) + return updateMetaData(instance, entity, auth, constants.API_ACTION.TaxonomyPutMetadata) } fullyUpdate.schema = { @@ -83,7 +99,7 @@ async function particallyUpdate (id, entity, auth) { serviceHelper.hasPermission(PERMISSION.UPDATE_TAXONOMY_METADATA, auth) } - return updateMetaData(instance, { ...instance.dataValues.metadata, ...entity }, auth) + return updateMetaData(instance, { ...instance.dataValues.metadata, ...entity }, auth, constants.API_ACTION.TaxonomyPatchMetadata) } particallyUpdate.schema = { @@ -108,7 +124,7 @@ async function remove (id, fields, auth) { throw errors.NotFoundError(`Metadata fields: ${nonExistingFields} do not exist`) } - return updateMetaData(instance, _.omit(instance.dataValues.metadata, fields), auth) + return updateMetaData(instance, _.omit(instance.dataValues.metadata, fields), auth, constants.API_ACTION.TaxonomyDeleteMetadata) } remove.schema = { diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 48a3fd9..f355fa0 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -132,7 +132,7 @@ const PERMISSION = { CREATE_TAXONOMY: { meta: { title: 'Create Taxonomy', - group: 'Taxonomy Metadata' + group: 'Taxonomy' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, scopes: SCOPES_PROJECTS_WRITE @@ -141,7 +141,7 @@ const PERMISSION = { UPDATE_TAXONOMY: { meta: { title: 'Update Taxonomy', - group: 'Taxonomy Metadata' + group: 'Taxonomy' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, scopes: SCOPES_PROJECTS_WRITE @@ -150,7 +150,7 @@ const PERMISSION = { DELETE_TAXONOMY: { meta: { title: 'Delete Taxonomy', - group: 'Taxonomy Metadata' + group: 'Taxonomy' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, scopes: SCOPES_PROJECTS_WRITE @@ -162,7 +162,7 @@ const PERMISSION = { ADD_TAXONOMY_METADATA: { meta: { title: 'Add Taxonomy Metadata', - group: 'Taxonomy', + group: 'Taxonomy Metadata', description: 'Add metadata fields in a taxonomy' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, @@ -172,7 +172,7 @@ const PERMISSION = { UPDATE_TAXONOMY_METADATA: { meta: { title: 'Update Taxonomy Metadata', - group: 'Taxonomy', + group: 'Taxonomy Metadata', description: 'Update Metadata fields from a taxonomy' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, @@ -182,7 +182,7 @@ const PERMISSION = { DELETE_TAXONOMY_METADATA: { meta: { title: 'Delete Taxonomy Metadata', - group: 'Taxonomy', + group: 'Taxonomy Metadata', description: 'Delete Metadata fields from a taxonomy' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS,