diff --git a/.circleci/config.yml b/.circleci/config.yml index bb8198d..a859c14 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,6 +68,7 @@ workflows: branches: only: - develop + - feature/enrich # Production builds are exectuted only on tagged commits to the # master branch. @@ -75,4 +76,4 @@ workflows: context : org-global filters: branches: - only: master \ No newline at end of file + only: master 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 2c7de80..42c4df4 100755 --- a/config/default.js +++ b/config/default.js @@ -20,10 +20,25 @@ 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', ES_REFRESH: process.env.ES_REFRESH || 'true', + ES_API_VERSION: process.env.ES_API_VERSION || "7.4", ELASTICCLOUD: { id: process.env.ELASTICCLOUD_ID, @@ -35,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..9be6aca 100644 --- a/docs/permissions.html +++ b/docs/permissions.html @@ -97,9 +97,8 @@

- all:connect_project - all:projects - write:projects + create:skill + all:skill
@@ -127,9 +126,8 @@

- all:connect_project - all:projects - write:projects + update:skill + all:skill
@@ -157,9 +155,8 @@

- all:connect_project - all:projects - write:projects + delete:skill + all:skill
@@ -194,9 +191,8 @@

- all:connect_project - all:projects - write:projects + create:skill + all:skill
@@ -224,9 +220,8 @@

- all:connect_project - all:projects - write:projects + update:skill + all:skill
@@ -254,16 +249,15 @@

- all:connect_project - all:projects - write:projects + delete:skill + all:skill

- Taxonomy Metadata + Taxonomy

@@ -291,9 +285,8 @@

- all:connect_project - all:projects - write:projects + create:taxonomy + all:taxonomy
@@ -321,9 +314,8 @@

- all:connect_project - all:projects - write:projects + update:taxonomy + all:taxonomy
@@ -351,16 +343,15 @@

- all:connect_project - all:projects - write:projects + delete:taxonomy + all:taxonomy

- Taxonomy + Taxonomy Metadata

@@ -388,9 +379,8 @@

- all:connect_project - all:projects - write:projects + create:taxonomy + all:taxonomy
@@ -418,9 +408,8 @@

- all:connect_project - all:projects - write:projects + update:taxonomy + all:taxonomy
@@ -448,9 +437,8 @@

- all:connect_project - all:projects - write:projects + delete:taxonomy + all:taxonomy
diff --git a/docs/skills-api.postman_collection.json b/docs/skills-api.postman_collection.json index 7f62ddb..12af166 100644 --- a/docs/skills-api.postman_collection.json +++ b/docs/skills-api.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "807bc545-d28c-4725-b88a-1bca2e734a50", + "_postman_id": "8d22bd52-0b64-43a5-9db3-c8516cb3e371", "name": "skills-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -39,7 +39,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"taxonomy_06\",\n \"metadata\": {}\n}" + "raw": "{\n \"name\": \"taxonomy_06\",\n \"metadata\": {\n \"random_field_01\": \"random_value_01\"\n }\n}" }, "url": { "raw": "{{HOST}}/taxonomies", @@ -108,8 +108,13 @@ { "name": "{{HOST}}/taxonomies/:id", "request": { - "method": "HEAD", + "method": "PUT", "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, { "key": "Content-Type", "name": "Content-Type", @@ -117,6 +122,10 @@ "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"taxonomies_update\",\n \"metadata\": {\n \"new_metadata_field\": \"value\"\n }\n}" + }, "url": { "raw": "{{HOST}}/taxonomies/{{taxonomyId}}", "host": [ @@ -130,50 +139,11 @@ }, "response": [] }, - { - "name": "{{HOST}}/taxonomies", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{HOST}}/taxonomies", - "host": [ - "{{HOST}}" - ], - "path": [ - "taxonomies" - ] - } - }, - "response": [] - }, - { - "name": "{{HOST}}/taxonomies", - "request": { - "method": "HEAD", - "header": [], - "url": { - "raw": "{{HOST}}/taxonomies", - "host": [ - "{{HOST}}" - ], - "path": [ - "taxonomies" - ] - } - }, - "response": [] - }, { "name": "{{HOST}}/taxonomies/:id", "request": { - "method": "DELETE", + "method": "HEAD", "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token}}" - }, { "key": "Content-Type", "name": "Content-Type", @@ -181,10 +151,6 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{HOST}}/taxonomies/{{taxonomyId}}", "host": [ @@ -197,77 +163,36 @@ } }, "response": [] - } - ] - }, - { - "name": "taxonomies metadata", - "item": [ + }, { - "name": "{{HOST}}/taxonomies/:id", + "name": "{{HOST}}/taxonomies", "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token}}" - }, - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"version\": \"1.0.1\",\n \"isLongTermSupport\": true\n}" - }, + "method": "GET", + "header": [], "url": { - "raw": "{{HOST}}/taxonomies/{{taxonomyId}}/metadata", + "raw": "{{HOST}}/taxonomies", "host": [ "{{HOST}}" ], "path": [ - "taxonomies", - "{{taxonomyId}}", - "metadata" + "taxonomies" ] } }, "response": [] }, { - "name": "{{HOST}}/taxonomies/:id", + "name": "{{HOST}}/taxonomies", "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token}}" - }, - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"version\": \"1.0.1\",\n \"endSupportDate\": \"2031-01-01\"\n}" - }, + "method": "HEAD", + "header": [], "url": { - "raw": "{{HOST}}/taxonomies/{{taxonomyId}}/metadata", + "raw": "{{HOST}}/taxonomies", "host": [ "{{HOST}}" ], "path": [ - "taxonomies", - "{{taxonomyId}}", - "metadata" + "taxonomies" ] } }, @@ -292,17 +217,16 @@ ], "body": { "mode": "raw", - "raw": "[\n \"isLongTermSupport\",\n \"endSupportDate\"\n]" + "raw": "" }, "url": { - "raw": "{{HOST}}/taxonomies/{{taxonomyId}}/metadata", + "raw": "{{HOST}}/taxonomies/{{taxonomyId}}", "host": [ "{{HOST}}" ], "path": [ "taxonomies", - "{{taxonomyId}}", - "metadata" + "{{taxonomyId}}" ] } }, @@ -395,7 +319,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"name\":\"skill_name_update23\"\n}" + "raw": "{\n \"name\": \"skill_name_update23\",\n \"metadata\": {\n \"memberProminence\": \"0.75\",\n \"version\": \"0.1.0\"\n }\n}" }, "url": { "raw": "{{HOST}}/skills/{{skillId}}", @@ -413,8 +337,13 @@ { "name": "{{HOST}}/skills/:id", "request": { - "method": "HEAD", + "method": "PUT", "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{token}}" + }, { "key": "Content-Type", "name": "Content-Type", @@ -422,6 +351,10 @@ "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n\t\"taxonomyId\":\"{{taxonomyId}}\",\n\t\"name\":\"jump6\",\n \"metadata\": {\n \"challengeProminence\": \"0.2\"\n }\n}" + }, "url": { "raw": "{{HOST}}/skills/{{skillId}}", "host": [ @@ -435,57 +368,11 @@ }, "response": [] }, - { - "name": "{{HOST}}/skills", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{HOST}}/skills", - "host": [ - "{{HOST}}" - ], - "path": [ - "skills" - ], - "query": [ - { - "key": "perPage", - "value": "2", - "disabled": true - } - ] - } - }, - "response": [] - }, - { - "name": "{{HOST}}/skills", - "request": { - "method": "HEAD", - "header": [], - "url": { - "raw": "{{HOST}}/skills", - "host": [ - "{{HOST}}" - ], - "path": [ - "skills" - ] - } - }, - "response": [] - }, { "name": "{{HOST}}/skills/:id", "request": { - "method": "DELETE", + "method": "HEAD", "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token}}" - }, { "key": "Content-Type", "name": "Content-Type", @@ -493,10 +380,6 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "{{HOST}}/skills/{{skillId}}", "host": [ @@ -509,77 +392,43 @@ } }, "response": [] - } - ] - }, - { - "name": "skills metadata", - "item": [ + }, { - "name": "{{HOST}}/skills/:id", + "name": "{{HOST}}/skills", "request": { - "method": "PUT", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token}}" - }, - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"version\": \"1.0.1\",\n \"isLongTermSupport\": true\n}" - }, + "method": "GET", + "header": [], "url": { - "raw": "{{HOST}}/skills/{{skillId}}/metadata", + "raw": "{{HOST}}/skills", "host": [ "{{HOST}}" ], "path": [ - "skills", - "{{skillId}}", - "metadata" + "skills" + ], + "query": [ + { + "key": "perPage", + "value": "2", + "disabled": true + } ] } }, "response": [] }, { - "name": "{{HOST}}/skills/:id", + "name": "{{HOST}}/skills", "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "type": "text", - "value": "Bearer {{token}}" - }, - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"version\": \"1.0.1\",\n \"endSupportDate\": \"2031-01-01\"\n}" - }, + "method": "HEAD", + "header": [], "url": { - "raw": "{{HOST}}/skills/{{skillId}}/metadata", + "raw": "{{HOST}}/skills", "host": [ "{{HOST}}" ], "path": [ - "skills", - "{{skillId}}", - "metadata" + "skills" ] } }, @@ -604,17 +453,16 @@ ], "body": { "mode": "raw", - "raw": "[\n \"isLongTermSupport\",\n \"endSupportDate\"\n]" + "raw": "" }, "url": { - "raw": "{{HOST}}/skills/{{skillId}}/metadata", + "raw": "{{HOST}}/skills/{{skillId}}", "host": [ "{{HOST}}" ], "path": [ "skills", - "{{skillId}}", - "metadata" + "{{skillId}}" ] } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 13ce870..02d4fbf 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -30,12 +30,8 @@ basePath: "/api/1.0" tags: - name: "Skills" description: "Skills registered in the system" - - name: "Skill Metadata" - description: "Metadata for Skills registered in the system" - name: "Taxonomies" description: "Taxonomies registered in the system" - - name: "Taxonomy Metadata" - description: "Metadata for Taxonomies registered in the system" schemes: - "https" consumes: @@ -285,16 +281,16 @@ paths: tags: - "Skills" description: > - Update an existing skill with given id. + Partically update an existing skill with given id. **PERMISSION** 1. permission `UPDATE_SKILL` is required to perform this operation - 2. permission `ADD_SKILL_METADATA` is required if any metadata field is provided. + 2. permission `ADD_SKILL_METADATA` is required if any new metadata field is provided. - 3. permission `DELETE_SKILL_METADATA` is required if there is any existing metadata field in the skill. + 3. permission `UPDATE_SKILL_METADATA` is required if any metadata field provided already exist in the skill. operationId: "skillsSkillIdPATCH" parameters: - name: "skillId" @@ -329,105 +325,21 @@ paths: - Bearer: [] x-swagger-router-controller: "Skills" - /skills/{skillId}/metadata: put: tags: - - "Skill Metadata" - description: > - Fully update the metadata of an existing skill with given id. - - Used to entirely overwrite existing metadata. - - - **PERMISSION** - - 1. permission `ADD_SKILL_METADATA` is required if any metadata field is provided. - - 2. permission `DELETE_SKILL_METADATA` is required if there is any existing metadata field in the skill. - parameters: - - name: "skillId" - in: "path" - description: "The skill id" - required: true - type: "string" - format: "UUID" - - in: "body" - name: "body" - required: true - schema: - $ref: "#/definitions/SkillMetadataUpdateRequestBody" - responses: - "200": - description: "OK - the request was successful" - schema: - $ref: "#/definitions/Skill" - "400": - $ref: "#/definitions/BadRequest" - "401": - $ref: "#/definitions/Unauthorized" - "403": - $ref: "#/definitions/Forbidden" - "404": - $ref: "#/definitions/NotFound" - "500": - $ref: "#/definitions/ServerError" - security: - - Bearer: [] - patch: - tags: - - "Skill Metadata" + - "Skills" description: > - Partically update the metadata of an existing skill with given id. - - Used to update existing fields in metadata and to add new fields to metadata. + Fully update an existing skill with given id. **PERMISSION** - 1. permission `ADD_SKILL_METADATA` is required if any new metadata field is provided. - - 2. permission `UPDATE_SKILL_METADATA` is required if any metadata field provided already exist in the skill. - parameters: - - name: "skillId" - in: "path" - description: "The skill id" - required: true - type: "string" - format: "UUID" - - in: "body" - name: "body" - required: true - schema: - $ref: "#/definitions/SkillMetadataUpdateRequestBody" - responses: - "200": - description: "OK - the request was successful" - schema: - $ref: "#/definitions/Skill" - "400": - $ref: "#/definitions/BadRequest" - "401": - $ref: "#/definitions/Unauthorized" - "403": - $ref: "#/definitions/Forbidden" - "404": - $ref: "#/definitions/NotFound" - "500": - $ref: "#/definitions/ServerError" - security: - - Bearer: [] - delete: - tags: - - "Skill Metadata" - description: > - Remove fields from the metadata of an existing skill with given id. - - 404 error will be raised if one or more of the provided fields is missing in the existing metadata. - + 1. permission `UPDATE_SKILL` is required to perform this operation - **PERMISSION** + 2. permission `ADD_SKILL_METADATA` is required if new metadata fields are provided. - Permission `DELETE_SKILL_METADATA` is required to perform this operation. + 3. permission `DELETE_SKILL_METADATA` is required if there is any existing metadata field in the skill. + operationId: "skillsSkillIdPUT" parameters: - name: "skillId" in: "path" @@ -439,7 +351,7 @@ paths: name: "body" required: true schema: - $ref: "#/definitions/SkillMetadataDeleteRequestBody" + $ref: "#/definitions/SkillRequestBody" responses: "200": description: "OK - the request was successful" @@ -453,10 +365,13 @@ paths: $ref: "#/definitions/Forbidden" "404": $ref: "#/definitions/NotFound" + "409": + $ref: "#/definitions/Conflict" "500": $ref: "#/definitions/ServerError" security: - Bearer: [] + x-swagger-router-controller: "Skills" /taxonomies: get: @@ -674,16 +589,16 @@ paths: tags: - "Taxonomies" description: > - Update an existing taxonomy with given id. + Partically update an existing taxonomy with given id. **PERMISSION** 1. permission `UPDATE_TAXONOMY` is required to perform this operation - 2. permission `ADD_TAXONOMY_METADATA` is required if any metadata field is provided. + 2. permission `ADD_TAXONOMY_METADATA` is required if any new metadata field is provided. - 3. permission `DELETE_TAXONOMY_METADATA` is required if there is any existing metadata field in the taxonomy. + 3. permission `UPDATE_TAXONOMY_METADATA` is required if any metadata field provided already exist in the taxonomy. operationId: "skillstaxonomiestaxonomyIdPATCH" parameters: - name: "taxonomyId" @@ -717,105 +632,21 @@ paths: security: - Bearer: [] x-swagger-router-controller: "Taxonomy" - /taxonomies/{taxonomyId}/metadata: put: tags: - - "Taxonomy Metadata" - description: > - Fully update the metadata of an existing taxonomy with given id. - - Used to entirely overwrite existing metadata. - - - **PERMISSION** - - 1. permission `ADD_TAXONOMY_METADATA` is required if any metadata field is provided. - - 2. permission `DELETE_TAXONOMY_METADATA` is required if there is any existing metadata field in the taxonomy. - parameters: - - name: "taxonomyId" - in: "path" - description: "The taxonomy id" - required: true - type: "string" - format: "UUID" - - in: "body" - name: "body" - required: true - schema: - $ref: "#/definitions/TaxonomyMetadataUpdateRequestBody" - responses: - "200": - description: "OK - the request was successful" - schema: - $ref: "#/definitions/Taxonomy" - "400": - $ref: "#/definitions/BadRequest" - "401": - $ref: "#/definitions/Unauthorized" - "403": - $ref: "#/definitions/Forbidden" - "404": - $ref: "#/definitions/NotFound" - "500": - $ref: "#/definitions/ServerError" - security: - - Bearer: [] - patch: - tags: - - "Taxonomy Metadata" + - "Taxonomies" description: > - Partically update the metadata of an existing taxonomy with given id. - - Used to update existing fields in metadata and to add new fields to metadata. + Fully update an existing taxonomy with given id. **PERMISSION** - 1. permission `ADD_TAXONOMY_METADATA` is required if any new metadata field is provided. - - 2. permission `UPDATE_TAXONOMY_METADATA` is required if any metadata field provided already exist in the taxonomy. - parameters: - - name: "taxonomyId" - in: "path" - description: "The taxonomy id" - required: true - type: "string" - format: "UUID" - - in: "body" - name: "body" - required: true - schema: - $ref: "#/definitions/TaxonomyMetadataUpdateRequestBody" - responses: - "200": - description: "OK - the request was successful" - schema: - $ref: "#/definitions/Taxonomy" - "400": - $ref: "#/definitions/BadRequest" - "401": - $ref: "#/definitions/Unauthorized" - "403": - $ref: "#/definitions/Forbidden" - "404": - $ref: "#/definitions/NotFound" - "500": - $ref: "#/definitions/ServerError" - security: - - Bearer: [] - delete: - tags: - - "Taxonomy Metadata" - description: > - Remove fields from the metadata of an existing taxonomy with given id. - - 404 error will be raised if one or more of the provided fields is missing in the existing metadata. - + 1. permission `UPDATE_TAXONOMY` is required to perform this operation - **PERMISSION** + 2. permission `ADD_TAXONOMY_METADATA` is required if any metadata field is provided. - Permission `DELETE_TAXONOMY_METADATA` is required to perform this operation. + 3. permission `DELETE_TAXONOMY_METADATA` is required if there is any existing metadata field in the taxonomy. + operationId: "skillstaxonomiestaxonomyIdPUT" parameters: - name: "taxonomyId" in: "path" @@ -827,7 +658,7 @@ paths: name: "body" required: true schema: - $ref: "#/definitions/TaxonomyMetadataDeleteRequestBody" + $ref: "#/definitions/TaxonomyRequestBody" responses: "200": description: "OK - the request was successful" @@ -841,10 +672,13 @@ paths: $ref: "#/definitions/Forbidden" "404": $ref: "#/definitions/NotFound" + "409": + $ref: "#/definitions/Conflict" "500": $ref: "#/definitions/ServerError" security: - Bearer: [] + x-swagger-router-controller: "Taxonomy" securityDefinitions: Bearer: @@ -954,26 +788,6 @@ definitions: metadata: challengeProminence: "challengeProminence" memberProminence: "memberProminence" - SkillMetadataUpdateRequestBody: - type: "object" - description: "The metadata for a skill. Can contain arbitrary fields" - properties: - challengeProminence: - type: "string" - description: "The challenge prominence ranging from [0, 1]" - memberProminence: - type: "string" - description: "The member prominence ranging from [0, 1]" - example: - verions: "1.0.0" - random_field: "random_value" - SkillMetadataDeleteRequestBody: - type: "array" - description: "The list of fields to be removed from the metadata for a skill." - items: - type: string - description: "field name" - example: ["version", "random_field"] Taxonomy: allOf: - type: "object" @@ -1015,19 +829,8 @@ definitions: description: "The metadata of the taxonomy." example: name: "name" - TaxonomyMetadataUpdateRequestBody: - type: "object" - description: "The metadata for a taxonomy. Can contain arbitrary fields" - example: - verions: "1.0.0" - random_field: "random_value" - TaxonomyMetadataDeleteRequestBody: - type: "array" - description: "The list of fields to be removed from the metadata for a taxonomy." - items: - type: string - description: "field name" - example: ["version", "random_field"] + metadata: + random_field_name_01: random_value_01 Unauthorized: type: "object" properties: diff --git a/package-lock.json b/package-lock.json index 9da86ce..25baf97 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", @@ -230,6 +240,14 @@ "debug": "4" } }, + "agentkeepalive": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "requires": { + "humanize-ms": "^1.2.1" + } + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -290,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", @@ -307,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", @@ -321,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", @@ -344,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", @@ -386,11 +674,42 @@ "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", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "aws-sdk": { + "version": "2.961.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.961.0.tgz", + "integrity": "sha512-71ELXHWd0roRT0FW8CnqGLYGKLksHARa7Yn8LPN3mF70FJt2LuvVAyK49IChoUczTjlzS78p+Y5197Tkky4N8g==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -430,6 +749,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -604,6 +928,16 @@ "fill-range": "^7.0.1" } }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -705,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", @@ -758,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", @@ -767,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", @@ -787,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", @@ -840,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", @@ -896,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", @@ -1004,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", @@ -1115,6 +1534,48 @@ "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=" + } + } + }, + "emitter-listener": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", + "requires": { + "shimmer": "^1.2.0" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1148,6 +1609,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", @@ -1197,8 +1663,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { "version": "6.8.0", @@ -1609,6 +2074,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -1666,6 +2136,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", @@ -1849,6 +2327,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", @@ -1864,6 +2355,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", @@ -2040,6 +2536,14 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, "has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -2064,6 +2568,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", @@ -2074,6 +2583,11 @@ "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==" }, + "http-aws-es": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/http-aws-es/-/http-aws-es-4.0.0.tgz", + "integrity": "sha512-5OJVj9/JSNOVFgIOnBK+9fwDePd35PF1odskYjp/aqstuurZy1XdmHoDP+wPE5LH9Pe/TasIJyARyH7aJnLh/A==" + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -2121,6 +2635,14 @@ "debug": "4" } }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "requires": { + "ms": "^2.0.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2129,6 +2651,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -2177,6 +2704,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", @@ -2302,6 +2834,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", @@ -2331,6 +2878,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", @@ -2354,20 +2906,57 @@ "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-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", + "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", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { @@ -2429,6 +3018,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", @@ -2469,6 +3066,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", @@ -2480,17 +3082,45 @@ "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=" }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "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" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2650,6 +3280,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", @@ -2664,6 +3307,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", @@ -2767,6 +3418,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", @@ -2844,6 +3518,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", @@ -2907,6 +3589,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", @@ -3060,6 +3758,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", @@ -3072,6 +3823,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", @@ -3522,6 +4281,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", @@ -3573,6 +4337,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", @@ -3643,6 +4556,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3674,6 +4592,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", @@ -3754,6 +4682,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", @@ -3926,6 +4947,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, "secure-json-parse": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.4.0.tgz", @@ -4046,11 +5072,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", @@ -4066,6 +5125,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", @@ -4168,6 +5232,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", @@ -4202,6 +5271,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", @@ -4268,12 +5407,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", @@ -4368,6 +5566,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", @@ -4385,6 +5608,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", @@ -4394,6 +5622,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", @@ -4409,11 +5645,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", @@ -4646,6 +5905,22 @@ "punycode": "^2.1.0" } }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -4705,6 +5980,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", @@ -4889,6 +6169,20 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "dev": true }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 266cca9..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,10 +36,14 @@ "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", "umzug": "^2.3.0", + "aws-sdk": "^2.610.0", + "http-aws-es": "^4.0.0", + "elasticsearch": "^16.1.1", "winston": "^3.2.1" }, "devDependencies": { 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/controller-helper.js b/src/common/controller-helper.js index 9b6b188..d00f8c4 100644 --- a/src/common/controller-helper.js +++ b/src/common/controller-helper.js @@ -24,6 +24,15 @@ function getControllerMethods (service) { res.json(await service.patch(req.params.id, req.body, req.authUser)) } + /** + * fully update entity by id + * @param req the http request + * @param res the http response + */ + async function fullyUpdate (req, res) { + res.json(await service.fullyUpdate(req.params.id, req.body, req.authUser)) + } + /** * get entity by id * @param req the http request @@ -59,7 +68,8 @@ function getControllerMethods (service) { search, remove, get, - patch + patch, + fullyUpdate } } 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-client.js b/src/common/es-client.js index 0bc306b..8995282 100644 --- a/src/common/es-client.js +++ b/src/common/es-client.js @@ -26,9 +26,18 @@ function getESClient () { } }) } else { - esClient = new elasticsearch.Client({ - node: host - }) + if (/.*amazonaws.*/.test(host)) { + esClient = new elasticsearch.Client({ + apiVersion: config.get('ES.ES_API_VERSION'), + node: host, + connectionClass: require('http-aws-es'), // eslint-disable-line global-require + }); + console.log('esClient=> ', esClient) + } else { + esClient = new elasticsearch.Client({ + node: host + }) + } } return esClient } 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..82ff35b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -40,10 +40,37 @@ const M2M_SCOPES = { PROJECTS: { ALL: 'all:projects', WRITE: 'write:projects' + }, + SKILLS: { + ALL: 'all:skill', + CREATE: 'create:skill', + READ: 'read:skill', + UPDATE: 'update:skill', + DELETE: 'delete:skill' + }, + TAXONOMIES: { + ALL: 'all:taxonomy', + CREATE: 'create:taxonomy', + READ: 'read:taxonomy', + UPDATE: 'update:taxonomy', + DELETE: 'delete:taxonomy' } } +const SequelizeCLSNamespace = 'skills-api' + +const API_ACTION = { + SkillCreate: 'skill.create', + SkillUpdate: 'skill.update', + SkillDelete: 'skill.delete', + TaxonomyCreate: 'taxonomy.create', + TaxonomyUpdate: 'taxonomy.update', + TaxonomyDelete: 'taxonomy.delete' +} + 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/controller.js b/src/modules/SkillMetadata/controller.js deleted file mode 100644 index b9db49f..0000000 --- a/src/modules/SkillMetadata/controller.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * the skill sub-controller, specific to the metadata fields - */ - -const service = require('./service') - -/** - * Fully update skill metadata. - * @param req the http request - * @param res the http response - */ -async function fullyUpdate (req, res) { - res.json(await service.fullyUpdate(req.params.id, req.body, req.authUser)) -} - -/** - * Partically update skill metadata. - * @param req the http request - * @param res the http response - */ -async function particallyUpdate (req, res) { - res.json(await service.particallyUpdate(req.params.id, req.body, req.authUser)) -} - -/** - * Remove one or more fields from skill metadata. - * @param req the http request - * @param res the http response - */ -async function remove (req, res) { - res.json(await service.remove(req.params.id, req.body, req.authUser)) -} - -module.exports = { - fullyUpdate, - particallyUpdate, - remove -} diff --git a/src/modules/SkillMetadata/route.js b/src/modules/SkillMetadata/route.js deleted file mode 100644 index a3df454..0000000 --- a/src/modules/SkillMetadata/route.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * the skill sub-routes, specific to the metadata fields - */ - -const Controller = require('./controller') - -module.exports = { - '/skills/:id/metadata': { - put: { - method: Controller.fullyUpdate, - auth: 'jwt' - }, - patch: { - method: Controller.particallyUpdate, - auth: 'jwt' - }, - delete: { - method: Controller.remove, - auth: 'jwt', - permission: 'skill.deleteMetadata' - } - } -} diff --git a/src/modules/SkillMetadata/service.js b/src/modules/SkillMetadata/service.js deleted file mode 100644 index 5c9e6a9..0000000 --- a/src/modules/SkillMetadata/service.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * the skill sub-services, specific to the metadata fields - */ - -const joi = require('@hapi/joi') -const _ = require('lodash') - -const errors = require('../../common/errors') -const helper = require('../../common/helper') -const dbHelper = require('../../common/db-helper') -const serviceHelper = require('../../common/service-helper') -const { PERMISSION } = require('../../permissions/constants') -const sequelize = require('../../models/index') - -const Skill = sequelize.models.Skill -const Taxonomy = sequelize.models.Taxonomy -const resource = serviceHelper.getResource('Skill') - -/** - * update skill metadata - * @param instance the skill instance - * @param metadata the new metadata - * @param auth the auth object - * @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) -} - -/** - * Fully update skill metadata - * @param id the skill id - * @param entity the request skill metadata - * @param auth the auth object - * @return the updated skill - */ -async function fullyUpdate (id, entity, auth) { - const instance = await dbHelper.get(Skill, id) - - if (Object.keys(entity).length) { - // check permission for adding new fields - serviceHelper.hasPermission(PERMISSION.ADD_SKILL_METADATA, auth) - } - if (Object.keys(instance.metadata).length) { - // check permission for removing existing fields - serviceHelper.hasPermission(PERMISSION.DELETE_SKILL_METADATA, auth) - } - - return updateMetaData(instance, entity, auth) -} - -fullyUpdate.schema = { - id: joi.string().uuid().required(), - entity: joi.object().keys({ - challengeProminence: joi.prominence('challengeProminence'), - memberProminence: joi.prominence('memberProminence') - }).unknown(true).required(), - auth: joi.object() -} - -/** - * Partically update skill metadata - * @param id the skill id - * @param entity the request skill metadata - * @param auth the auth object - * @return the updated skill - */ -async function particallyUpdate (id, entity, auth) { - const instance = await dbHelper.get(Skill, id) - - const inputFields = Object.keys(entity) - const existingFields = Object.keys(instance.metadata) - const sharedFields = _.intersection(inputFields, existingFields) - - if (inputFields.length > sharedFields.length) { - // check permission for adding new fields - serviceHelper.hasPermission(PERMISSION.ADD_SKILL_METADATA, auth) - } - if (sharedFields.length) { - // check permission for updating fields - serviceHelper.hasPermission(PERMISSION.UPDATE_SKILL_METADATA, auth) - } - - return updateMetaData(instance, { ...instance.dataValues.metadata, ...entity }, auth) -} - -particallyUpdate.schema = { - id: joi.string().uuid().required(), - entity: joi.object().keys({ - challengeProminence: joi.prominence('challengeProminence'), - memberProminence: joi.prominence('memberProminence') - }).min(1).unknown(true).required(), - auth: joi.object() -} - -/** - * Remove one or more fields from skill metadata - * @param id the skill id - * @param fields the list of field names - * @param auth the auth object - * @return the updated skill - */ -async function remove (id, fields, auth) { - const instance = await dbHelper.get(Skill, id) - - const existingFields = Object.keys(instance.metadata) - const nonExistingFields = fields.filter(field => !existingFields.includes(field)) - if (nonExistingFields.length) { - throw errors.NotFoundError(`Metadata fields: ${nonExistingFields} do not exist`) - } - - return updateMetaData(instance, _.omit(instance.dataValues.metadata, fields), auth) -} - -remove.schema = { - id: joi.string().uuid().required(), - fields: joi.array().min(1).items(joi.string()).required(), - auth: joi.object() -} - -module.exports = { - fullyUpdate, - particallyUpdate, - remove -} 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/route.js b/src/modules/skill/route.js index bd29bcb..2c07f3c 100644 --- a/src/modules/skill/route.js +++ b/src/modules/skill/route.js @@ -30,6 +30,11 @@ module.exports = { auth: 'jwt', permission: 'skill.edit' }, + put: { + method: Controller.fullyUpdate, + auth: 'jwt', + permission: 'skill.edit' + }, delete: { method: Controller.remove, auth: 'jwt', diff --git a/src/modules/skill/service.js b/src/modules/skill/service.js index aa024d7..594d362 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 = { @@ -55,46 +69,78 @@ create.schema = { } /** - * patch skill by id + * Update skill by id. Used in functions patch and fullyUpdate. + * + * @param instance the skill instance + * @param updateData the data to be updated + * @param auth the auth object + * @return the updated skill + */ +async function update (instance, updateData, auth) { + let payload + try { + return await sequelize.transaction(async () => { + const newEntity = await instance.update({ + ...updateData, + updatedBy: helper.getAuthUser(auth) + }) + + payload = newEntity.dataValues + + const 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 skill by id. + * If the metadata field is provided, existing metadata fields would be updated and new metadata fields would be added. + * * @param id the skill id * @param entity the request skill entity * @param auth the auth object * @return the updated skill */ async function patch (id, entity, auth) { - let taxonomy - if (entity.taxonomyId) { - taxonomy = await dbHelper.get(Taxonomy, entity.taxonomyId) + // check if the skill exists or not + const instance = await dbHelper.get(Skill, id) + + if (entity.taxonomyId && entity.taxonomyId !== instance.taxonomyId) { + // check if the taxonomy exists or not + await dbHelper.get(Taxonomy, entity.taxonomyId) } + // check if the skill has conflict or not await dbHelper.makeSureUnique(Skill, entity, uniqueFields) - const instance = await dbHelper.get(Skill, id) - if (entity.metadata) { - if (Object.keys(entity.metadata).length) { - // check permission for adding new metadata fields + const inputFields = Object.keys(entity.metadata) + const existingFields = Object.keys(instance.metadata) + const sharedFields = _.intersection(inputFields, existingFields) + + if (inputFields.length > sharedFields.length) { + // check permission for adding new fields serviceHelper.hasPermission(PERMISSION.ADD_SKILL_METADATA, auth) } - if (Object.keys(instance.metadata).length) { - // check permission for removing existing metadata fields - serviceHelper.hasPermission(PERMISSION.DELETE_SKILL_METADATA, auth) + if (sharedFields.length) { + // check permission for updating fields + serviceHelper.hasPermission(PERMISSION.UPDATE_SKILL_METADATA, auth) } } - const newEntity = await instance.update({ - ...entity, - updatedBy: helper.getAuthUser(auth) - }) + const updateData = { ...instance, ...entity, metadata: { ...instance.metadata, ...entity.metadata } } - if (!taxonomy) { - taxonomy = await dbHelper.get(Taxonomy, newEntity.taxonomyId) - } - const updated = newEntity.dataValues - updated.taxonomyName = taxonomy.name - await serviceHelper.patchRecordInEs(resource, updated) - - return helper.omitAuditFields(updated) + return update(instance, updateData, auth) } patch.schema = { @@ -112,6 +158,56 @@ patch.schema = { auth: joi.object() } +/** + * Fully update skill by id. + * Existing metadata fields would be entirely replace with the new ones. + * + * @param id the skill id + * @param entity the request skill entity + * @param auth the auth object + * @return the updated skill + */ +async function fullyUpdate (id, entity, auth) { + // check if the skill exists or not + const instance = await dbHelper.get(Skill, id) + + if (entity.taxonomyId !== instance.taxonomyId) { + // check if the taxonomy exists or not + await dbHelper.get(Taxonomy, entity.taxonomyId) + } + + // check if the skill has conflict or not + await dbHelper.makeSureUnique(Skill, entity, uniqueFields) + + if (Object.keys(entity.metadata).length) { + // check permission for adding new metadata fields + serviceHelper.hasPermission(PERMISSION.ADD_SKILL_METADATA, auth) + } + if (Object.keys(instance.metadata).length) { + // check permission for removing existing metadata fields + serviceHelper.hasPermission(PERMISSION.DELETE_SKILL_METADATA, auth) + } + + const updateData = entity + + return update(instance, updateData, auth) +} + +fullyUpdate.schema = { + id: joi.string().uuid().required(), + entity: joi.object().keys({ + taxonomyId: joi.string().uuid().required(), + name: joi.string().required(), + uri: joi.string().default(null), + externalId: joi.string().default(null), + metadata: joi.object().keys({ + challengeProminence: joi.prominence('challengeProminence'), + memberProminence: joi.prominence('memberProminence') + }).unknown(true).required() + }).required(), + auth: joi.object() +} + /** * get skill by id * @param id the skill id @@ -124,8 +220,8 @@ async function get (id, params, query = {}, fromDb = false) { const trueParams = _.assign(params, query) if (!fromDb) { const esResult = await serviceHelper.getRecordInEs(resource, id, trueParams) - await populateTaxonomyNames(esResult) if (esResult) { + await populateTaxonomyNames(esResult) return helper.omitAuditFields(esResult) } } @@ -198,7 +294,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 +311,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 = { @@ -229,6 +333,7 @@ module.exports = { create, search, patch, + fullyUpdate, get, remove } diff --git a/src/modules/taxonomy/route.js b/src/modules/taxonomy/route.js index 1586fa4..64177e2 100644 --- a/src/modules/taxonomy/route.js +++ b/src/modules/taxonomy/route.js @@ -30,6 +30,11 @@ module.exports = { auth: 'jwt', permission: 'taxonomy.edit' }, + put: { + method: Controller.fullyUpdate, + auth: 'jwt', + permission: 'taxonomy.edit' + }, delete: { method: Controller.remove, auth: 'jwt', diff --git a/src/modules/taxonomy/service.js b/src/modules/taxonomy/service.js index f4f06f7..6defe8f 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 = { @@ -43,7 +57,40 @@ create.schema = { } /** - * patch taxonomy by id + * Update taxonomy by id. Used in functions patch and fullyUpdate. + * + * @param instance the taxonomy instance + * @param updateData the data to be updated + * @param auth the auth object + * @return the updated taxonomy + */ +async function update (instance, updateData, auth) { + let payload + try { + return await sequelize.transaction(async () => { + const newEntity = await instance.update({ + ...updateData, + 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 taxonomy by id. + * If the metadata field is provided, existing metadata fields would be updated and new metadata fields would be added. + * * @param id the taxonomy id * @param entity the request taxonomy entity * @param auth the auth object @@ -53,6 +100,48 @@ create.schema = { async function patch (id, entity, auth) { const instance = await dbHelper.get(Taxonomy, id) + if (entity.metadata) { + const inputFields = Object.keys(entity.metadata) + const existingFields = Object.keys(instance.metadata) + const sharedFields = _.intersection(inputFields, existingFields) + + if (inputFields.length > sharedFields.length) { + // check permission for adding new fields + serviceHelper.hasPermission(PERMISSION.ADD_TAXONOMY_METADATA, auth) + } + if (sharedFields.length) { + // check permission for updating fields + serviceHelper.hasPermission(PERMISSION.UPDATE_TAXONOMY_METADATA, auth) + } + } + + const updateData = { ...instance, ...entity, metadata: { ...instance.metadata, ...entity.metadata } } + + return update(instance, updateData, auth) +} + +patch.schema = { + id: joi.string().uuid().required(), + entity: joi.object().keys({ + name: joi.string(), + metadata: joi.object() + }).min(1).required(), + auth: joi.object() +} + +/** + * Fully update taxonomy by id. + * Existing metadata fields would be entirely replace with the new ones. + * + * @param id the taxonomy id + * @param entity the request taxonomy entity + * @param auth the auth object + * @param params the query params + * @return the updated taxonomy + */ +async function fullyUpdate (id, entity, auth) { + const instance = await dbHelper.get(Taxonomy, id) + if (entity.metadata) { if (Object.keys(entity.metadata).length) { // check permission for adding new metadata fields @@ -64,21 +153,17 @@ async function patch (id, entity, auth) { } } - const newEntity = await instance.update({ - ...entity, - updatedBy: helper.getAuthUser(auth) - }) + const updateData = entity - await serviceHelper.patchRecordInEs(resource, newEntity.dataValues) - return helper.omitAuditFields(newEntity.dataValues) + return update(instance, updateData, auth) } -patch.schema = { +fullyUpdate.schema = { id: joi.string().uuid().required(), entity: joi.object().keys({ - name: joi.string(), - metadata: joi.object() - }).min(1).required(), + name: joi.string().required(), + metadata: joi.object().default({}) + }).required(), auth: joi.object() } @@ -135,7 +220,7 @@ async function search (query) { search.schema = { query: { - page: joi.string().uuid(), + page: joi.page(), perPage: joi.pageSize(), name: joi.string() } @@ -154,8 +239,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 = { @@ -168,6 +261,7 @@ module.exports = { create, search, patch, + fullyUpdate, get, remove } diff --git a/src/modules/taxonomyMetadata/controller.js b/src/modules/taxonomyMetadata/controller.js deleted file mode 100644 index e37a5db..0000000 --- a/src/modules/taxonomyMetadata/controller.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * the taxonomy sub-controller, specific to the metadata fields - */ - -const service = require('./service') - -/** - * Fully update taxonomy metadata. - * @param req the http request - * @param res the http response - */ -async function fullyUpdate (req, res) { - res.json(await service.fullyUpdate(req.params.id, req.body, req.authUser)) -} - -/** - * Partically update taxonomy metadata. - * @param req the http request - * @param res the http response - */ -async function particallyUpdate (req, res) { - res.json(await service.particallyUpdate(req.params.id, req.body, req.authUser)) -} - -/** - * Remove one or more fields from taxonomy metadata. - * @param req the http request - * @param res the http response - */ -async function remove (req, res) { - res.json(await service.remove(req.params.id, req.body, req.authUser)) -} - -module.exports = { - fullyUpdate, - particallyUpdate, - remove -} diff --git a/src/modules/taxonomyMetadata/route.js b/src/modules/taxonomyMetadata/route.js deleted file mode 100644 index ba53e28..0000000 --- a/src/modules/taxonomyMetadata/route.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * the taxonomy sub-routes, specific to the metadata fields - */ - -const Controller = require('./controller') - -module.exports = { - '/taxonomies/:id/metadata': { - put: { - method: Controller.fullyUpdate, - auth: 'jwt' - }, - patch: { - method: Controller.particallyUpdate, - auth: 'jwt' - }, - delete: { - method: Controller.remove, - auth: 'jwt', - permission: 'taxonomy.deleteMetadata' - } - } -} diff --git a/src/modules/taxonomyMetadata/service.js b/src/modules/taxonomyMetadata/service.js deleted file mode 100644 index 2395d8d..0000000 --- a/src/modules/taxonomyMetadata/service.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * the taxonomy sub-services, specific to the metadata fields - */ - -const joi = require('@hapi/joi') -const _ = require('lodash') - -const errors = require('../../common/errors') -const helper = require('../../common/helper') -const dbHelper = require('../../common/db-helper') -const serviceHelper = require('../../common/service-helper') -const { PERMISSION } = require('../../permissions/constants') -const sequelize = require('../../models/index') - -const Taxonomy = sequelize.models.Taxonomy -const resource = serviceHelper.getResource('Taxonomy') - -/** - * update taxonomy metadata - * @param instance the taxonomy instance - * @param metadata the new metadata - * @param auth the auth object - * @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) -} - -/** - * Fully update taxonomy metadata - * @param id the taxonomy id - * @param entity the request taxonomy metadata - * @param auth the auth object - * @return the updated taxonomy - */ -async function fullyUpdate (id, entity, auth) { - const instance = await dbHelper.get(Taxonomy, id) - - if (Object.keys(entity).length) { - // check permission for adding new fields - serviceHelper.hasPermission(PERMISSION.ADD_TAXONOMY_METADATA, auth) - } - if (Object.keys(instance.metadata).length) { - // check permission for removing existing fields - serviceHelper.hasPermission(PERMISSION.DELETE_TAXONOMY_METADATA, auth) - } - - return updateMetaData(instance, entity, auth) -} - -fullyUpdate.schema = { - id: joi.string().uuid().required(), - entity: joi.object().required(), - auth: joi.object() -} - -/** - * Partically update taxonomy metadata - * @param id the taxonomy id - * @param entity the request taxonomy metadata - * @param auth the auth object - * @return the updated taxonomy - */ -async function particallyUpdate (id, entity, auth) { - const instance = await dbHelper.get(Taxonomy, id) - - const inputFields = Object.keys(entity) - const existingFields = Object.keys(instance.metadata) - const sharedFields = _.intersection(inputFields, existingFields) - - if (inputFields.length > sharedFields.length) { - // check permission for adding new fields - serviceHelper.hasPermission(PERMISSION.ADD_TAXONOMY_METADATA, auth) - } - if (sharedFields.length) { - // check permission for updating fields - serviceHelper.hasPermission(PERMISSION.UPDATE_TAXONOMY_METADATA, auth) - } - - return updateMetaData(instance, { ...instance.dataValues.metadata, ...entity }, auth) -} - -particallyUpdate.schema = { - id: joi.string().uuid().required(), - entity: joi.object().min(1).required(), - auth: joi.object() -} - -/** - * Remove one or more fields from taxonomy metadata - * @param id the taxonomy id - * @param fields the list of field names - * @param auth the auth object - * @return the updated taxonomy - */ -async function remove (id, fields, auth) { - const instance = await dbHelper.get(Taxonomy, id) - - const existingFields = Object.keys(instance.metadata) - const nonExistingFields = fields.filter(field => !existingFields.includes(field)) - if (nonExistingFields.length) { - throw errors.NotFoundError(`Metadata fields: ${nonExistingFields} do not exist`) - } - - return updateMetaData(instance, _.omit(instance.dataValues.metadata, fields), auth) -} - -remove.schema = { - id: joi.string().uuid().required(), - fields: joi.array().min(1).items(joi.string()).required(), - auth: joi.object() -} - -module.exports = { - fullyUpdate, - particallyUpdate, - remove -} diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 48a3fd9..028bd16 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -72,7 +72,7 @@ const PERMISSION = { group: 'Skill' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.SKILLS.CREATE, M2M_SCOPES.SKILLS.ALL] }, UPDATE_SKILL: { @@ -81,7 +81,7 @@ const PERMISSION = { group: 'Skill' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.SKILLS.UPDATE, M2M_SCOPES.SKILLS.ALL] }, DELETE_SKILL: { @@ -90,7 +90,7 @@ const PERMISSION = { group: 'Skill' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.SKILLS.DELETE, M2M_SCOPES.SKILLS.ALL] }, /* @@ -103,7 +103,7 @@ const PERMISSION = { description: 'Add metadata fields in a skill' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.SKILLS.CREATE, M2M_SCOPES.SKILLS.ALL] }, UPDATE_SKILL_METADATA: { @@ -113,7 +113,7 @@ const PERMISSION = { description: 'Update Metadata fields from a skill' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.SKILLS.UPDATE, M2M_SCOPES.SKILLS.ALL] }, DELETE_SKILL_METADATA: { @@ -123,7 +123,7 @@ const PERMISSION = { description: 'Delete Metadata fields from a skill' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.SKILLS.DELETE, M2M_SCOPES.SKILLS.ALL] }, /* @@ -132,28 +132,28 @@ const PERMISSION = { CREATE_TAXONOMY: { meta: { title: 'Create Taxonomy', - group: 'Taxonomy Metadata' + group: 'Taxonomy' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.TAXONOMIES.CREATE, M2M_SCOPES.TAXONOMIES.ALL] }, UPDATE_TAXONOMY: { meta: { title: 'Update Taxonomy', - group: 'Taxonomy Metadata' + group: 'Taxonomy' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.TAXONOMIES.UPDATE, M2M_SCOPES.TAXONOMIES.ALL] }, DELETE_TAXONOMY: { meta: { title: 'Delete Taxonomy', - group: 'Taxonomy Metadata' + group: 'Taxonomy' }, topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.TAXONOMIES.DELETE, M2M_SCOPES.TAXONOMIES.ALL] }, /* @@ -162,31 +162,31 @@ 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, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.TAXONOMIES.CREATE, M2M_SCOPES.TAXONOMIES.ALL] }, 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, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.TAXONOMIES.UPDATE, M2M_SCOPES.TAXONOMIES.ALL] }, 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, - scopes: SCOPES_PROJECTS_WRITE + scopes: [M2M_SCOPES.TAXONOMIES.DELETE, M2M_SCOPES.TAXONOMIES.ALL] } }