diff --git a/README.md b/README.md index 63e9f838..e0470e18 100644 --- a/README.md +++ b/README.md @@ -275,4 +275,5 @@ docker exec -it tc-projects-kafka /opt/kafka/bin/kafka-console-producer.sh --bro ## References -- [Projects Service Architecture](./docs/guides/architercture/architecture.md) \ No newline at end of file +- [Projects Service Architecture](./docs/guides/architercture/architecture.md) +- [Projects Service Architecture](./docs/guides/architercture/architecture.md) diff --git a/config/test.json b/config/test.json index 2453fc55..cb81054c 100644 --- a/config/test.json +++ b/config/test.json @@ -4,6 +4,7 @@ "logLevel": "debug", "captureLogs": "false", "logentriesToken": "", + "enableFileUpload": "false", "elasticsearchConfig": { "host": "http://localhost:9200", "apiVersion": "6.8", diff --git a/docs/Project API.postman_collection.json b/docs/Project API.postman_collection.json index 930185eb..6f3d9895 100644 --- a/docs/Project API.postman_collection.json +++ b/docs/Project API.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "49981b6e-f611-4016-999e-737ef4103435", + "_postman_id": "c69ab4dd-8c6a-48c9-ba48-c6663b1a0c81", "name": "Project API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -248,7 +248,7 @@ "_postman_isSubFolder": true }, { - "name": "Upload attachment", + "name": "Upload file attachment", "event": [ { "listen": "test", @@ -257,7 +257,7 @@ "exec": [ "pm.test(\"Status code is 201\", function () {", " pm.response.to.have.status(201);", - " pm.environment.set(\"attachmentId\", pm.response.json().id);", + " pm.environment.set(\"fileAttachmentId\", pm.response.json().id);", "});" ], "type": "text/javascript" @@ -278,7 +278,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\t\"title\": \"first attachment submission\",\n\t\t\"filePath\": \"/home/phoenix/a.png\",\n\t\t\"s3Bucket\": \"topcoder-project-service\",\n\t\t\"contentType\": \"application/png\"\n\t}" + "raw": "{\n\t\"title\": \"first file attachment\",\n\t\"path\": \"/home/files/phoenix/a.png\",\n\t\"type\": \"file\",\n\t\"s3Bucket\": \"topcoder-project-service\",\n\t\"contentType\": \"application/png\",\n\t\"tags\": [\"design preview\"]\n}" }, "url": { "raw": "{{api-url}}/projects/{{projectId}}/attachments", @@ -296,7 +296,55 @@ "response": [] }, { - "name": "Update attachment", + "name": "Create link as attachment", + "event": [ + { + "listen": "test", + "script": { + "id": "6547ada6-53f5-4e2d-bda0-f0ec5bfbe38f", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"linkAttachmentId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"title\": \"link attachment\",\n\t\"path\": \"https://connect.topcoder-dev.com/projects/8600/assets.zip\",\n\t\"type\": \"link\",\n\t\"tags\": [\"specification\", \"design preview\", \"billing information\"]\n}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/attachments", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "attachments" + ] + }, + "description": "Create an project attachment" + }, + "response": [] + }, + { + "name": "Update file attachment", "request": { "method": "PATCH", "header": [ @@ -311,10 +359,10 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\t\"title\": \"first attachment submission updated\",\n\t\t\"description\": \"updated project attachment\"\n\t}" + "raw": "{\n\t\t\"title\": \"first attachment submission updated\",\n\t\t\"description\": \"updated project attachment\",\n\t\t\"tags\": [\"anotherTag\"]\n\t}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{attachmentId}}", + "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{fileAttachmentId}}", "host": [ "{{api-url}}" ], @@ -322,7 +370,7 @@ "projects", "{{projectId}}", "attachments", - "{{attachmentId}}" + "{{fileAttachmentId}}" ] }, "description": "Update project attachment" @@ -330,9 +378,46 @@ "response": [] }, { - "name": "Delete attachment", + "name": "Update link attachment", "request": { - "method": "DELETE", + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"title\": \"updated link title\",\n\t\t\"description\": \"updated link description\",\n\t\t\"tags\": [\"linkTag1\"]\n\t}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{linkAttachmentId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "attachments", + "{{linkAttachmentId}}" + ] + }, + "description": "Update project attachment" + }, + "response": [] + }, + { + "name": "Get file attachment", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", "header": [ { "key": "Authorization", @@ -348,7 +433,7 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{attachmentId}}", + "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{fileAttachmentId}}", "host": [ "{{api-url}}" ], @@ -356,7 +441,7 @@ "projects", "{{projectId}}", "attachments", - "{{attachmentId}}" + "{{fileAttachmentId}}" ] }, "description": "Delete a project attachment" @@ -364,7 +449,7 @@ "response": [] }, { - "name": "Download attachment", + "name": "Get link attachment", "protocolProfileBehavior": { "disableBodyPruning": true }, @@ -385,7 +470,7 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{attachmentId}}", + "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{linkAttachmentId}}", "host": [ "{{api-url}}" ], @@ -393,7 +478,7 @@ "projects", "{{projectId}}", "attachments", - "{{attachmentId}}" + "{{linkAttachmentId}}" ] }, "description": "Delete a project attachment" @@ -435,6 +520,74 @@ "description": "Delete a project attachment" }, "response": [] + }, + { + "name": "Delete link attachment", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{linkAttachmentId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "attachments", + "{{linkAttachmentId}}" + ] + }, + "description": "Delete a project attachment" + }, + "response": [] + }, + { + "name": "Delete file attachment", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{fileAttachmentId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "attachments", + "{{fileAttachmentId}}" + ] + }, + "description": "Delete a project attachment" + }, + "response": [] } ], "protocolProfileBehavior": {} @@ -1033,6 +1186,41 @@ { "name": "Project Members Invites", "item": [ + { + "name": "List project member invite", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/invites", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "invites" + ] + } + }, + "response": [] + }, { "name": "Create project member with no payload", "request": { @@ -1044,23 +1232,29 @@ }, { "key": "Content-Type", - "value": "application/json" + "name": "Content-Type", + "value": "application/json", + "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n}" + "raw": "{\n}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/members/invite", + "raw": "{{api-url}}/projects/{{projectId}}/invites", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "members", - "invite" + "invites" ] }, "description": "Request payload is mandatory while creating project. If no request payload is specified this should result in 400 status code." @@ -1069,6 +1263,21 @@ }, { "name": "Create project customer with valid values", + "event": [ + { + "listen": "test", + "script": { + "id": "320b75fe-958d-44ee-b2d2-1716b7b3e207", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"inviteId2\", pm.response.json().success[0].id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -1078,29 +1287,128 @@ }, { "key": "Content-Type", - "value": "application/json" + "name": "Content-Type", + "value": "application/json", + "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n\t\"role\": \"customer\",\n\t\"emails\": [\"test@topcoder.com\"]\n}" + "raw": "{\n\t\"role\": \"customer\",\n\t\"emails\": [\"test@topcoder.com\"]\n}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/members/invite", + "raw": "{{api-url}}/projects/{{projectId}}/invites", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "members", - "invite" + "invites" ] }, "description": "If the request payload is valid, than project customer should be added. This should sync with the direct project is project is associated with direct project." }, "response": [] }, + { + "name": "Create member invites with handles", + "event": [ + { + "listen": "test", + "script": { + "id": "3835313a-bb42-487a-b17e-4d687535d7e5", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"inviteId\", pm.response.json().success[0].id);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"role\": \"copilot\",\n\t\"handles\": [\"test_copilot1\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/invites", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "invites" + ] + } + }, + "response": [] + }, + { + "name": "Create member invites with wrong roles", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"role\": \"manager\",\n\t\"handles\": [\"test_copilot1\", \"test_user1\"]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/invites", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "invites" + ] + } + }, + "response": [] + }, { "name": "Get project member invite", "protocolProfileBehavior": { @@ -1123,15 +1431,15 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/members/invite", + "raw": "{{api-url}}/projects/{{projectId}}/invites/{{inviteId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "members", - "invite" + "invites", + "{{inviteId}}" ] }, "description": "Update a project's member." @@ -1141,31 +1449,38 @@ { "name": "Update project member invite", "request": { - "method": "PUT", + "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer {{jwt-token-copilot-40051332}}" }, { "key": "Content-Type", - "value": "application/json" + "name": "Content-Type", + "value": "application/json", + "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n\t\"status\": \"accepted\",\n\t\"email\": \"test@topcoder.com\"\n}" + "raw": "{\n\t\"status\": \"accepted\"\n}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/members/invite", + "raw": "{{api-url}}/projects/{{projectId}}/invites/{{inviteId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "members", - "invite" + "invites", + "{{inviteId}}" ] }, "description": "Update a project's member." @@ -1173,9 +1488,9 @@ "response": [] }, { - "name": "wrong status", + "name": "Update project member invite - wrong status", "request": { - "method": "PUT", + "method": "PATCH", "header": [ { "key": "Authorization", @@ -1183,27 +1498,106 @@ }, { "key": "Content-Type", - "value": "application/json" + "name": "Content-Type", + "value": "application/json", + "type": "text" } ], "body": { "mode": "raw", - "raw": " {\n\t\"status\": \"wrong\"\n } " + "raw": " {\n\t\"status\": \"wrong\"\n } ", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/members/invite", + "raw": "{{api-url}}/projects/{{projectId}}/invites/{{inviteId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "members", - "invite" + "invites", + "{{inviteId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update project member invite - wrong user", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token-member2-40051335}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"status\": \"accepted\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/invites/{{inviteId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "invites", + "{{inviteId}}" ] } }, "response": [] + }, + { + "name": "Cancel project member invite", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}" + }, + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/invites/{{inviteId2}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "invites", + "{{inviteId2}}" + ] + }, + "description": "Update a project's member." + }, + "response": [] } ], "event": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6a975a85..4389b666 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -353,7 +353,7 @@ paths: required: true description: Specify only those properties that needs to be updated schema: - $ref: '#/definitions/NewProjectAttachment' + $ref: '#/definitions/UpdateProjectAttachment' responses: '200': description: Returns the newly created project @@ -2890,7 +2890,7 @@ paths: example: 'work.create': true 'workItem.edit': true - + '401': description: Unauthorized schema: @@ -3519,31 +3519,26 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' - '/projects/{projectId}/members/invite': + '/projects/{projectId}/invites': get: tags: - project member invite - operationId: getCurrentUserInvite + operationId: listProjectInvites security: - Bearer: [] - description: Retrieve the invite for current user. + description: >- + If user can "view" this project, he/she can get all invitations. + Otherwise user can only see his/her own invitation in this project. + If user has no invitation in this project or this project doesn't exist, an empty array will be returned. parameters: - $ref: '#/parameters/projectIdParam' responses: '200': description: The invite for current user schema: - $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure' - '400': - description: Bad request - schema: - $ref: '#/definitions/ErrorModel' - '401': - description: Unauthorized - schema: - $ref: '#/definitions/ErrorModel' - '404': - description: Invite not found + $ref: '#/definitions/ProjectMemberInviteListResult' + '403': + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '500': @@ -3567,27 +3562,54 @@ paths: schema: $ref: '#/definitions/AddProjectMemberInvitesRequest' responses: - '200': - description: Returns the newly created invite + '201': + description: Created schema: $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure' '400': description: Bad request schema: $ref: '#/definitions/ErrorModel' - '401': - description: Unauthorized + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/invites/{inviteId}': + get: + tags: + - project member invite + operationId: getProjectMemberInvite + security: + - Bearer: [] + description: >- + Get an invite. Users who can "view" this project can see this invitation. + User got invited by this inviteId can also see this invitation. + If project/invitation doesn't exist, or this invitation is not for logged-in user, it will return 404 response. + parameters: + - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/inviteIdParam' + responses: + '200': + description: Returns the newly updated invite + schema: + $ref: '#/definitions/ProjectMemberInvite' '403': description: Forbidden schema: $ref: '#/definitions/ErrorModel' + '404': + description: Not Found + schema: + $ref: '#/definitions/ErrorModel' '500': description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' - put: + patch: tags: - project member invite operationId: updateProjectMemberInvite @@ -3598,6 +3620,7 @@ paths: restriction will be applied based on role to be updated. parameters: - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/inviteIdParam' - in: body name: body required: true @@ -3607,19 +3630,50 @@ paths: '200': description: Returns the newly updated invite schema: - $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure' + $ref: '#/definitions/ProjectMemberInvite' '400': description: Bad request schema: $ref: '#/definitions/ErrorModel' - '401': - description: Unauthorized + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: Not Found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + delete: + tags: + - project member invite + operationId: deleteProjectMemberInvite + security: + - Bearer: [] + description: >- + Cancel an invite. All users who can access this endpoint, however more + restriction will be applied based on role to be cancelled. + parameters: + - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/inviteIdParam' + responses: + '204': + description: Cancel success + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' '403': description: Forbidden schema: $ref: '#/definitions/ErrorModel' + '404': + description: Not Found + schema: + $ref: '#/definitions/ErrorModel' '500': description: Internal Server Error schema: @@ -4794,6 +4848,13 @@ parameters: required: true type: integer format: int64 + inviteIdParam: + name: inviteId + in: path + description: project member invite identifier + required: true + type: integer + format: int64 definitions: ErrorModel: type: object @@ -5137,20 +5198,30 @@ definitions: title: Project attachment request type: object required: - - filePath - - s3Bucket + - path + - type - title - - contentType properties: - filePath: + path: type: string description: path where file is stored + type: + type: string + description: The attachment type, one of 'link' or 'file' + enum: + - link + - file + tags: + type: array + description: The attachment tags + items: + type: string s3Bucket: type: string description: The s3 bucket of attachment contentType: type: string - description: Uploaded file content type + description: Uploaded file content type (optional for 'link') title: type: string description: Name of the attachment @@ -5170,6 +5241,27 @@ definitions: type: integer format: int64 description: Users allowed to access the attachment + UpdateProjectAttachment: + title: Project attachment request + type: object + properties: + tags: + type: array + description: The attachment tags + items: + type: string + title: + type: string + description: Name of the attachment + description: + type: string + description: Optional description for the attached file. + allowedUsers: + type: array + items: + type: integer + format: int64 + description: Users allowed to access the attachment ProjectAttachment: title: Project attachment type: object @@ -5177,6 +5269,28 @@ definitions: id: type: number description: unique id for the attachment + projectId: + type: number + description: the projectId to which this attachment belongs + type: + type: string + description: The attachment type, one of 'file' or 'link' + enum: + - link + - file + tags: + type: array + description: The attachment tags array + items: + type: string + allowedUsers: + type: array + description: The array of ids of the users allowed to access this attachment + items: + type: number + path: + type: string + description: The attachment path size: type: number format: float @@ -5193,9 +5307,9 @@ definitions: description: type: string description: Optional description for the attached file. - downloadUrl: + url: type: string - description: download link for the attachment. + description: download link for the attachment of type file createdAt: type: string description: Datetime (GMT) when task was created @@ -6156,26 +6270,41 @@ definitions: type: object properties: success: - $ref: '#/definitions/ProjectMemberInvite' + type: array + items: + $ref: '#/definitions/ProjectMemberInvite' failed: type: array items: type: object + properties: + email: + description: invitation user email(Optional) + type: string + handle: + description: invitation user handle(Optional) + type: string + message: + description: create invitation error message + type: string + ProjectMemberInviteListResult: + type: array + items: + $ref: '#/definitions/ProjectMemberInvite' AddProjectMemberInvitesRequest: title: Add project member invites request object type: object properties: - userIds: - description: 'The user Id list, could not present with emails' + handles: + description: 'The user handle list, could not present with emails' type: array items: - type: integer - format: int64 + type: string emails: type: array items: type: string - description: 'The user email list, could not present with userIds' + description: 'The user email list, could not present with handles' role: description: The target role in the project type: string @@ -6187,13 +6316,6 @@ definitions: title: Update project member invite request object type: object properties: - userId: - type: integer - format: int64 - description: 'The user Id, could not present with email' - email: - type: string - description: 'The user email, could not present with userId' status: description: The invite status type: string diff --git a/local/full/docker-compose.base.yml b/local/full/docker-compose.base.yml index e9dd9cb9..02d50fe6 100644 --- a/local/full/docker-compose.base.yml +++ b/local/full/docker-compose.base.yml @@ -4,7 +4,7 @@ services: build: context: ./generic-tc-service args: - NODE_VERSION: 8.2.1 + NODE_VERSION: 8.11.3 GIT_URL: https://github.com/topcoder-platform/tc-notifications GIT_BRANCH: dev BYPASS_TOKEN_VALIDATION: 1 diff --git a/local/full/docker-compose.yml b/local/full/docker-compose.yml index d4477b7e..8a3d725d 100644 --- a/local/full/docker-compose.yml +++ b/local/full/docker-compose.yml @@ -46,7 +46,7 @@ services: build: context: ./generic-tc-service args: - NODE_VERSION: 8.2.1 + NODE_VERSION: 8.11.3 GIT_URL: https://github.com/topcoder-platform/tc-bus-api GIT_BRANCH: dev BYPASS_TOKEN_VALIDATION: 1 @@ -62,13 +62,18 @@ services: - KAFKA_URL=kafka:9093 - JWT_TOKEN_SECRET=secret - VALID_ISSUERS="[\"https://topcoder-newauth.auth0.com/\",\"https://api.topcoder-dev.com\"]" + - AUTH0_CLIENT_ID + - AUTH0_CLIENT_SECRET + - AUTH0_URL + - AUTH0_AUDIENCE + - AUTH0_PROXY_SERVER_URL project-processor-es: build: context: ./generic-tc-service args: NODE_VERSION: 8.11.3 GIT_URL: https://github.com/topcoder-platform/project-processor-es - GIT_BRANCH: develop + GIT_BRANCH: feature/link-attachments command: start kafka-client expose: - "5000" @@ -80,6 +85,11 @@ services: - PORT=5000 - KAFKA_URL=kafka:9093 - ES_HOST=esearch:9200 + - AUTH0_CLIENT_ID + - AUTH0_CLIENT_SECRET + - AUTH0_URL + - AUTH0_AUDIENCE + - AUTH0_PROXY_SERVER_URL tc-notifications-reset-db: extends: file: ./docker-compose.base.yml diff --git a/local/full/generic-tc-service/Dockerfile b/local/full/generic-tc-service/Dockerfile index 3c93b79b..e3113c7f 100644 --- a/local/full/generic-tc-service/Dockerfile +++ b/local/full/generic-tc-service/Dockerfile @@ -1,4 +1,4 @@ -ARG NODE_VERSION=8.2.1 +ARG NODE_VERSION=8.11.3 FROM node:$NODE_VERSION ARG GIT_URL diff --git a/local/full/kafka-client/topics.txt b/local/full/kafka-client/topics.txt index 9b62bbea..8068b510 100644 --- a/local/full/kafka-client/topics.txt +++ b/local/full/kafka-client/topics.txt @@ -5,7 +5,7 @@ connect.notification.project.canceled connect.notification.project.completed connect.notification.project.created connect.notification.project.fileUploaded -connect.notification.project.files.updated +connect.notification.project.attachment.updated connect.notification.project.linkCreated connect.notification.project.member.assignedAsOwner connect.notification.project.member.copilotJoined diff --git a/local/kafka/kafka-client/Dockerfile b/local/kafka/kafka-client/Dockerfile index 15c20839..d338a5c1 100644 --- a/local/kafka/kafka-client/Dockerfile +++ b/local/kafka/kafka-client/Dockerfile @@ -2,4 +2,5 @@ From wurstmeister/kafka WORKDIR /app/ COPY topics.txt . COPY create-topics.sh . +RUN chmod +x create-topics.sh ENTRYPOINT ["/app/create-topics.sh"] diff --git a/local/kafka/kafka-client/topics.txt b/local/kafka/kafka-client/topics.txt index 9b62bbea..8068b510 100644 --- a/local/kafka/kafka-client/topics.txt +++ b/local/kafka/kafka-client/topics.txt @@ -5,7 +5,7 @@ connect.notification.project.canceled connect.notification.project.completed connect.notification.project.created connect.notification.project.fileUploaded -connect.notification.project.files.updated +connect.notification.project.attachment.updated connect.notification.project.linkCreated connect.notification.project.member.assignedAsOwner connect.notification.project.member.copilotJoined diff --git a/local/mock-services/server.js b/local/mock-services/server.js index f43fd1c7..04c34b12 100644 --- a/local/mock-services/server.js +++ b/local/mock-services/server.js @@ -25,7 +25,7 @@ server.use(authMiddleware); server.get('/v3/members/_search', (req, res) => { const fields = _.isString(req.query.fields) ? req.query.fields.split(',') : []; const filter = _.isString(req.query.query) ? - req.query.query.replace('%2520', ' ').replace('%20', ' ').split(' OR ') : []; + req.query.query.replace(/%2520/g, ' ').replace(/%20/g, ' ').split(' OR ') : []; const criteria = _.map(filter, (single) => { const ret = {}; const splitted = single.split(':'); @@ -45,6 +45,7 @@ server.get('/v3/members/_search', (req, res) => { }); const userIds = _.map(criteria, 'userId'); const handles = _.map(criteria, 'handle'); + const handleLowers = _.map(criteria, 'handleLower'); const cloned = _.cloneDeep(members); const response = { id: 'res1', @@ -66,6 +67,12 @@ server.get('/v3/members/_search', (req, res) => { found = _.pick(found, fields); } return found; + } else if (_.indexOf(handleLowers, single.result.content.handleLower) > -1) { + let found = single.result.content; + if (fields.length > 0) { + found = _.pick(found, fields); + } + return found; } return null; }).filter(_.identity); @@ -76,7 +83,7 @@ server.get('/v3/members/_search', (req, res) => { // add filter route for project members server.get('/users', (req, res) => { - const filter = req.query.filter.replace('%2520', ' ').replace('%20', ' ').replace('%3D', ' '); + const filter = req.query.filter.replace(/%2520/g, ' ').replace(/%20/g, ' ').replace('%3D', ' '); const allEmails = filter.split('=')[1]; const emails = allEmails.split('OR'); const cloned = _.cloneDeep(members); @@ -97,7 +104,7 @@ server.get('/users', (req, res) => { // add additional search route for project members server.get('/roles', (req, res) => { const filter = _.isString(req.query.filter) ? - req.query.filter.replace('%2520', ' ').replace('%20', ' ').split('=') : []; + req.query.filter.replace(/%2520/g, ' ').replace(/%20/g, ' ').split('=') : []; const cloned = _.cloneDeep(roles); const response = { id: 'res1', diff --git a/migrations/20200213_update_project_attachments.sql b/migrations/20200213_update_project_attachments.sql new file mode 100644 index 00000000..cd07987a --- /dev/null +++ b/migrations/20200213_update_project_attachments.sql @@ -0,0 +1,19 @@ +-- UPDATE EXISTING project_attachments table +-- Add the following columns : type and tags +-- update column : rename file_path to path + +-- Add 'type' column, type is the attachment type : 'file' or 'link' +ALTER TABLE project_attachments ADD COLUMN "type" character varying(255); +-- As all existent data is files, set the "type" to file +UPDATE project_attachments SET "type"='file'; +-- Set not null +ALTER TABLE project_attachments ALTER COLUMN "type" SET NOT NULL; + +-- Add 'tags' column, the attachment tags +ALTER TABLE project_attachments ADD COLUMN tags character varying(255)[] NULL; + +-- Rename 'file_path' column to 'path' +ALTER TABLE project_attachments RENAME COLUMN "filePath" TO "path"; + +-- Make the contentType column Nullable +ALTER TABLE project_attachments ALTER COLUMN "contentType" DROP NOT NULL; \ No newline at end of file diff --git a/migrations/bookmarks/Project.db.dump.json b/migrations/bookmarks/Project.db.dump.json new file mode 100644 index 00000000..0968ded7 --- /dev/null +++ b/migrations/bookmarks/Project.db.dump.json @@ -0,0 +1,2853 @@ +[ + { + "id": 1, + "directProjectId": null, + "billingAccountId": null, + "name": "Develop website", + "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message", + "external": null, + "bookmarks": [ + { + "address": "https://connect.topcoder-dev.com/projects/8600/assets1", + "title": "Link without auditing fields, project audit fields should be used" + }, + { + "createdAt": "2020-02-10T10:37:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets2", + "updatedBy": 222, + "title": "link 2", + "updatedAt": "2020-02-10T10:37:47.000Z" + }, + { + "createdAt": "2020-01-01T17:09:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets3", + "updatedBy": 333, + "createdBy": 333, + "title": "link 3", + "updatedAt": "2020-01-02T17:09:50.199Z" + }, + { + "createdAt": "2020-02-03T10:37:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets4", + "updatedBy": 444, + "createdBy": 444, + "title": "link 4", + "updatedAt": "2020-02-04T10:37:47.000Z" + }, + { + "createdAt": "2020-01-05T17:09:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets5", + "createdBy": 555, + "title": "link 5" + }, + { + "address": "https://connect.topcoder-dev.com/projects/8600/assets6", + "updatedBy": 666, + "createdBy": 666, + "title": "link 6", + "updatedAt": "2020-02-08T10:37:47.000Z" + }, + { + "createdAt": "2020-01-09T17:09:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets7", + "updatedBy": 777, + "createdBy": 777, + "title": "link 7", + "updatedAt": "2020-01-10T17:09:50.199Z" + }, + { + "createdAt": "2020-02-11T10:37:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets8", + "updatedBy": 888, + "createdBy": 888, + "title": "link 8", + "updatedAt": "2020-02-12T10:37:47.000Z" + }, + { + "createdAt": "2020-01-13T17:09:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets9", + "updatedBy": 999, + "createdBy": 999, + "title": "link 9", + "updatedAt": "2020-01-14T17:09:50.199Z" + }, + { + "createdAt": "2020-02-15T10:37:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets10", + "title": "link 10", + "updatedAt": "2020-02-16T10:37:47.000Z" + } + ], + "utm": null, + "estimatedPrice": null, + "actualPrice": null, + "terms": [], + "type": "chatbot", + "status": "in_review", + "details": { + "utm": {}, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" + }, + "users": { + "value": "No one" + } + }, + "hideDiscussions": true + }, + "challengeEligibility": [], + "cancelReason": null, + "templateId": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:39.872Z", + "updatedAt": "2019-12-26T20:33:39.873Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "version": "v2", + "lastActivityAt": "2019-12-26T20:33:39.844Z", + "lastActivityUserId": "40051333", + "phases": [], + "invites": [], + "attachments": [ + { + "projectId": 1, + "title": "file1.txt", + "description": "blah", + "contentType": "application/unknown", + "size": 12312, + "category": "categ1", + "path": "https://media.topcoder.com/projects/1/test.txt", + "type": "file", + "tags": ["tag1", "tag2"], + "createdBy": 1, + "updatedBy": 1 + } + ], + "members": [ + { + "id": 1, + "userId": 40051333, + "role": "manager", + "isPrimary": true, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:39.873Z", + "updatedAt": "2019-12-26T20:33:39.908Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 1 + } + ] + }, + { + "id": 3, + "directProjectId": null, + "billingAccountId": null, + "name": "Develop chatbot", + "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message", + "external": null, + "bookmarks": [], + "utm": null, + "estimatedPrice": null, + "actualPrice": null, + "terms": [], + "type": "chatbot", + "status": "active", + "details": { + "utm": {}, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" + }, + "users": { + "value": "No one" + } + }, + "hideDiscussions": true + }, + "challengeEligibility": [], + "cancelReason": null, + "templateId": 4, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.016Z", + "updatedAt": "2019-12-26T20:33:43.561Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "version": "v3", + "lastActivityAt": "2019-12-26T20:33:39.844Z", + "lastActivityUserId": "40051333", + "phases": [ + { + "id": 22, + "name": "Front-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.141Z", + "updatedAt": "2019-12-26T20:33:40.141Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 3, + "products": [ + { + "id": 30, + "name": "Development Iteration (3 Milestones)", + "projectId": 3, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.286Z", + "updatedAt": "2019-12-26T20:33:40.286Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 22 + } + ] + }, + { + "id": 23, + "name": "Back-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.142Z", + "updatedAt": "2019-12-26T20:33:40.142Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 3, + "products": [ + { + "id": 19, + "name": "Development Iteration (3 Milestones)", + "projectId": 3, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.287Z", + "updatedAt": "2019-12-26T20:33:40.287Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 23 + } + ] + }, + { + "id": 26, + "name": "Front-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.141Z", + "updatedAt": "2019-12-26T20:33:40.141Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 3, + "products": [ + { + "id": 24, + "name": "Development Iteration (3 Milestones)", + "projectId": 3, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.288Z", + "updatedAt": "2019-12-26T20:33:40.288Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 26 + } + ] + }, + { + "id": 27, + "name": "QA & Bug Fixes", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-18T00:00:00.000Z", + "duration": 24, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.142Z", + "updatedAt": "2019-12-26T20:33:40.142Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 3, + "products": [ + { + "id": 22, + "name": "QA Iteration", + "projectId": 3, + "directProjectId": null, + "billingAccountId": null, + "templateId": 30, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.288Z", + "updatedAt": "2019-12-26T20:33:40.288Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 27 + } + ] + }, + { + "id": 28, + "name": "Chatbot Design", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-19T00:00:00.000Z", + "duration": 25, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.140Z", + "updatedAt": "2019-12-26T20:33:40.141Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 3, + "products": [ + { + "id": 29, + "name": "Design Iteration (long)", + "projectId": 3, + "directProjectId": null, + "billingAccountId": null, + "templateId": 26, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.289Z", + "updatedAt": "2019-12-26T20:33:40.289Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 28 + } + ] + }, + { + "id": 29, + "name": "Back-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.141Z", + "updatedAt": "2019-12-26T20:33:40.141Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 3, + "products": [ + { + "id": 26, + "name": "Development Iteration (3 Milestones)", + "projectId": 3, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.289Z", + "updatedAt": "2019-12-26T20:33:40.289Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 29 + } + ] + } + ], + "invites": [], + "attachments": [], + "members": [ + { + "id": 4, + "userId": 40051333, + "role": "manager", + "isPrimary": true, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.016Z", + "updatedAt": "2019-12-26T20:33:40.054Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 3 + } + ] + }, + { + "id": 4, + "directProjectId": null, + "billingAccountId": null, + "name": "Develop app", + "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message", + "external": null, + "bookmarks": [], + "utm": null, + "estimatedPrice": null, + "actualPrice": null, + "terms": [], + "type": "app", + "status": "in_review", + "details": { + "utm": { + "code": "R&D" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" + }, + "users": { + "value": "No one" + } + }, + "hideDiscussions": true + }, + "challengeEligibility": [], + "cancelReason": null, + "templateId": 1, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.017Z", + "updatedAt": "2019-12-26T20:33:40.017Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "version": "v3", + "lastActivityAt": "2019-12-26T20:33:39.839Z", + "lastActivityUserId": "40051333", + "phases": [ + { + "id": 11, + "name": "Front-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.114Z", + "updatedAt": "2019-12-26T20:33:40.114Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 4, + "products": [ + { + "id": 5, + "name": "Development Iteration (3 Milestones)", + "projectId": 4, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.191Z", + "updatedAt": "2019-12-26T20:33:40.191Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 11 + } + ] + }, + { + "id": 12, + "name": "Website Design", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-19T00:00:00.000Z", + "duration": 25, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.113Z", + "updatedAt": "2019-12-26T20:33:40.113Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 4, + "products": [ + { + "id": 15, + "name": "Design Iteration (long)", + "projectId": 4, + "directProjectId": null, + "billingAccountId": null, + "templateId": 26, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.192Z", + "updatedAt": "2019-12-26T20:33:40.192Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 12 + } + ] + }, + { + "id": 13, + "name": "Back-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.115Z", + "updatedAt": "2019-12-26T20:33:40.115Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 4, + "products": [ + { + "id": 13, + "name": "Development Iteration (3 Milestones)", + "projectId": 4, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.193Z", + "updatedAt": "2019-12-26T20:33:40.193Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 13 + } + ] + }, + { + "id": 14, + "name": "Front-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.114Z", + "updatedAt": "2019-12-26T20:33:40.114Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 4, + "products": [ + { + "id": 11, + "name": "Development Iteration (3 Milestones)", + "projectId": 4, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.192Z", + "updatedAt": "2019-12-26T20:33:40.192Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 14 + } + ] + }, + { + "id": 15, + "name": "QA & Bug Fixes", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-18T00:00:00.000Z", + "duration": 24, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.116Z", + "updatedAt": "2019-12-26T20:33:40.116Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 4, + "products": [ + { + "id": 12, + "name": "QA Iteration", + "projectId": 4, + "directProjectId": null, + "billingAccountId": null, + "templateId": 30, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.192Z", + "updatedAt": "2019-12-26T20:33:40.192Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 15 + } + ] + }, + { + "id": 17, + "name": "Back-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.115Z", + "updatedAt": "2019-12-26T20:33:40.115Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 4, + "products": [ + { + "id": 18, + "name": "Development Iteration (3 Milestones)", + "projectId": 4, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.238Z", + "updatedAt": "2019-12-26T20:33:40.238Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 17 + } + ] + } + ], + "invites": [], + "attachments": [], + "members": [ + { + "id": 3, + "userId": 40051333, + "role": "manager", + "isPrimary": true, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.017Z", + "updatedAt": "2019-12-26T20:33:40.055Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 4 + } + ] + }, + { + "id": 5, + "directProjectId": null, + "billingAccountId": null, + "name": "Develop website 2", + "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message", + "external": null, + "bookmarks": [ + { + "address": "https://connect.topcoder-dev.com/projects/8600/assets11", + "title": "Project 5 link, project audit fields should be used" + }, + { + "createdAt": "2020-01-10T11:10:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets12", + "updatedBy": 123, + "title": "link 12", + "updatedAt": "2020-02-10T10:11:47.000Z" + }, + { + "createdAt": "2020-01-01T17:12:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets13", + "updatedBy": 124, + "createdBy": 124, + "title": "link 13", + "updatedAt": "2020-01-02T17:13:50.199Z" + }, + { + "createdAt": "2020-02-03T10:37:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets14", + "updatedBy": 125, + "createdBy": 125, + "title": "link 14", + "updatedAt": "2020-02-04T10:14:47.000Z" + }, + { + "createdAt": "2020-01-05T17:09:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets15", + "createdBy": 126, + "title": "link 15" + }, + { + "address": "https://connect.topcoder-dev.com/projects/8600/assets16", + "updatedBy": 127, + "createdBy": 127, + "title": "link 16", + "updatedAt": "2020-02-08T10:15:47.000Z" + }, + { + "createdAt": "2020-01-09T17:16:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets17", + "updatedBy": 128, + "createdBy": 128, + "title": "link 17", + "updatedAt": "2020-01-10T17:17:50.199Z" + }, + { + "createdAt": "2020-02-11T10:18:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets18", + "updatedBy": 129, + "createdBy": 129, + "title": "link 18", + "updatedAt": "2020-02-12T10:19:47.000Z" + }, + { + "createdAt": "2020-01-13T17:20:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets19", + "updatedBy": 130, + "createdBy": 130, + "title": "link 19", + "updatedAt": "2020-01-14T17:21:50.199Z" + }, + { + "createdAt": "2020-02-15T10:22:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets20", + "title": "link 20", + "updatedAt": "2020-02-16T10:23:47.000Z" + } + ], + "utm": null, + "estimatedPrice": null, + "actualPrice": null, + "terms": [], + "type": "website", + "status": "reviewed", + "details": { + "utm": {}, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" + }, + "users": { + "value": "No one" + } + }, + "hideDiscussions": true + }, + "challengeEligibility": [], + "cancelReason": null, + "templateId": 3, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.031Z", + "updatedAt": "2019-12-26T20:33:43.426Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "version": "v3", + "lastActivityAt": "2019-12-26T20:33:39.858Z", + "lastActivityUserId": "40051333", + "phases": [ + { + "id": 5, + "name": "App Design", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-19T00:00:00.000Z", + "duration": 25, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.111Z", + "updatedAt": "2019-12-26T20:33:40.111Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 5, + "products": [ + { + "id": 2, + "name": "Design Iteration (long)", + "projectId": 5, + "directProjectId": null, + "billingAccountId": null, + "templateId": 26, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.190Z", + "updatedAt": "2019-12-26T20:33:40.190Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 5 + } + ] + }, + { + "id": 6, + "name": "Front-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.112Z", + "updatedAt": "2019-12-26T20:33:40.112Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 5, + "products": [ + { + "id": 3, + "name": "Development Iteration (3 Milestones)", + "projectId": 5, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.190Z", + "updatedAt": "2019-12-26T20:33:40.190Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 6 + } + ] + }, + { + "id": 7, + "name": "Back-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.113Z", + "updatedAt": "2019-12-26T20:33:40.113Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 5, + "products": [ + { + "id": 4, + "name": "Development Iteration (3 Milestones)", + "projectId": 5, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.191Z", + "updatedAt": "2019-12-26T20:33:40.191Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 7 + } + ] + }, + { + "id": 9, + "name": "Back-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.112Z", + "updatedAt": "2019-12-26T20:33:40.112Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 5, + "products": [ + { + "id": 14, + "name": "Development Iteration (3 Milestones)", + "projectId": 5, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.193Z", + "updatedAt": "2019-12-26T20:33:40.193Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 9 + } + ] + }, + { + "id": 10, + "name": "QA & Bug Fixes", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-18T00:00:00.000Z", + "duration": 24, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.113Z", + "updatedAt": "2019-12-26T20:33:40.113Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 5, + "products": [ + { + "id": 7, + "name": "QA Iteration", + "projectId": 5, + "directProjectId": null, + "billingAccountId": null, + "templateId": 30, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.191Z", + "updatedAt": "2019-12-26T20:33:40.191Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 10 + } + ] + }, + { + "id": 16, + "name": "Front-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.112Z", + "updatedAt": "2019-12-26T20:33:40.112Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 5, + "products": [ + { + "id": 10, + "name": "Development Iteration (3 Milestones)", + "projectId": 5, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.192Z", + "updatedAt": "2019-12-26T20:33:40.192Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 16 + } + ] + } + ], + "invites": [], + "attachments": [], + "members": [ + { + "id": 5, + "userId": 40051333, + "role": "manager", + "isPrimary": true, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.031Z", + "updatedAt": "2019-12-26T20:33:40.069Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 5 + } + ] + }, + { + "id": 6, + "directProjectId": null, + "billingAccountId": null, + "name": "Reviewed project with copilot invited", + "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message", + "external": null, + "bookmarks": [], + "utm": null, + "estimatedPrice": null, + "actualPrice": null, + "terms": [], + "type": "website", + "status": "reviewed", + "details": { + "utm": {}, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" + }, + "users": { + "value": "No one" + } + }, + "hideDiscussions": true + }, + "challengeEligibility": [], + "cancelReason": null, + "templateId": 3, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.040Z", + "updatedAt": "2019-12-26T20:33:43.438Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "version": "v3", + "lastActivityAt": "2019-12-26T20:33:39.859Z", + "lastActivityUserId": "40051333", + "phases": [ + { + "id": 1, + "name": "App Design", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-19T00:00:00.000Z", + "duration": 25, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.108Z", + "updatedAt": "2019-12-26T20:33:40.108Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 6, + "products": [ + { + "id": 6, + "name": "Design Iteration (long)", + "projectId": 6, + "directProjectId": null, + "billingAccountId": null, + "templateId": 26, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.189Z", + "updatedAt": "2019-12-26T20:33:40.189Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 1 + } + ] + }, + { + "id": 2, + "name": "Front-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.108Z", + "updatedAt": "2019-12-26T20:33:40.108Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 6, + "products": [ + { + "id": 17, + "name": "Development Iteration (3 Milestones)", + "projectId": 6, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.234Z", + "updatedAt": "2019-12-26T20:33:40.234Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 2 + } + ] + }, + { + "id": 3, + "name": "QA & Bug Fixes", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-18T00:00:00.000Z", + "duration": 24, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.110Z", + "updatedAt": "2019-12-26T20:33:40.110Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 6, + "products": [ + { + "id": 1, + "name": "QA Iteration", + "projectId": 6, + "directProjectId": null, + "billingAccountId": null, + "templateId": 30, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.189Z", + "updatedAt": "2019-12-26T20:33:40.189Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 3 + } + ] + }, + { + "id": 4, + "name": "Back-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.109Z", + "updatedAt": "2019-12-26T20:33:40.109Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 6, + "products": [ + { + "id": 9, + "name": "Development Iteration (3 Milestones)", + "projectId": 6, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.190Z", + "updatedAt": "2019-12-26T20:33:40.190Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 4 + } + ] + }, + { + "id": 8, + "name": "Back-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.110Z", + "updatedAt": "2019-12-26T20:33:40.110Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 6, + "products": [ + { + "id": 8, + "name": "Development Iteration (3 Milestones)", + "projectId": 6, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.191Z", + "updatedAt": "2019-12-26T20:33:40.191Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 8 + } + ] + }, + { + "id": 18, + "name": "Front-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.109Z", + "updatedAt": "2019-12-26T20:33:40.109Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 6, + "products": [ + { + "id": 16, + "name": "Development Iteration (3 Milestones)", + "projectId": 6, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.233Z", + "updatedAt": "2019-12-26T20:33:40.233Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 18 + } + ] + } + ], + "invites": [ + { + "id": 1, + "projectId": 6, + "userId": 40152855, + "email": null, + "role": "copilot", + "status": "pending", + "createdAt": "2019-12-26T20:33:43.571Z", + "updatedAt": "2019-12-26T20:33:43.571Z", + "deletedAt": null, + "createdBy": 40051336, + "updatedBy": 40051336, + "deletedBy": null + } + ], + "attachments": [], + "members": [ + { + "id": 6, + "userId": 40051333, + "role": "manager", + "isPrimary": true, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.040Z", + "updatedAt": "2019-12-26T20:33:40.073Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 6 + } + ] + }, + { + "id": 7, + "directProjectId": null, + "billingAccountId": null, + "name": "Develop app 3", + "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message", + "external": null, + "bookmarks": [], + "utm": null, + "estimatedPrice": null, + "actualPrice": null, + "terms": [], + "type": "app", + "status": "cancelled", + "details": { + "utm": {}, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" + }, + "users": { + "value": "No one" + } + }, + "hideDiscussions": true + }, + "challengeEligibility": [], + "cancelReason": "Test cancel", + "templateId": 1, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.039Z", + "updatedAt": "2019-12-26T20:33:43.544Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "version": "v3", + "lastActivityAt": "2019-12-26T20:33:39.859Z", + "lastActivityUserId": "40051333", + "phases": [ + { + "id": 19, + "name": "Website Design", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-19T00:00:00.000Z", + "duration": 25, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.137Z", + "updatedAt": "2019-12-26T20:33:40.137Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 7, + "products": [ + { + "id": 28, + "name": "Design Iteration (long)", + "projectId": 7, + "directProjectId": null, + "billingAccountId": null, + "templateId": 26, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.290Z", + "updatedAt": "2019-12-26T20:33:40.290Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 19 + } + ] + }, + { + "id": 20, + "name": "Front-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.137Z", + "updatedAt": "2019-12-26T20:33:40.137Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 7, + "products": [ + { + "id": 27, + "name": "Development Iteration (3 Milestones)", + "projectId": 7, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.290Z", + "updatedAt": "2019-12-26T20:33:40.290Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 20 + } + ] + }, + { + "id": 21, + "name": "Front-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.138Z", + "updatedAt": "2019-12-26T20:33:40.138Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 7, + "products": [ + { + "id": 23, + "name": "Development Iteration (3 Milestones)", + "projectId": 7, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.286Z", + "updatedAt": "2019-12-26T20:33:40.286Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 21 + } + ] + }, + { + "id": 24, + "name": "QA & Bug Fixes", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-18T00:00:00.000Z", + "duration": 24, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.140Z", + "updatedAt": "2019-12-26T20:33:40.140Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 7, + "products": [ + { + "id": 20, + "name": "QA Iteration", + "projectId": 7, + "directProjectId": null, + "billingAccountId": null, + "templateId": 30, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.287Z", + "updatedAt": "2019-12-26T20:33:40.287Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 24 + } + ] + }, + { + "id": 25, + "name": "Back-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.138Z", + "updatedAt": "2019-12-26T20:33:40.139Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 7, + "products": [ + { + "id": 21, + "name": "Development Iteration (3 Milestones)", + "projectId": 7, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.288Z", + "updatedAt": "2019-12-26T20:33:40.288Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 25 + } + ] + }, + { + "id": 30, + "name": "Back-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.139Z", + "updatedAt": "2019-12-26T20:33:40.139Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 7, + "products": [ + { + "id": 25, + "name": "Development Iteration (3 Milestones)", + "projectId": 7, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.290Z", + "updatedAt": "2019-12-26T20:33:40.290Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 30 + } + ] + } + ], + "invites": [], + "attachments": [], + "members": [ + { + "id": 7, + "userId": 40051333, + "role": "manager", + "isPrimary": true, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.039Z", + "updatedAt": "2019-12-26T20:33:40.076Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 7 + } + ] + }, + { + "id": 8, + "directProjectId": null, + "billingAccountId": null, + "name": "Develop app 2", + "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message", + "external": null, + "bookmarks": [], + "utm": null, + "estimatedPrice": null, + "actualPrice": null, + "terms": [], + "type": "app", + "status": "completed", + "details": { + "utm": {}, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" + }, + "users": { + "value": "No one" + } + }, + "hideDiscussions": true + }, + "challengeEligibility": [], + "cancelReason": null, + "templateId": 1, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.045Z", + "updatedAt": "2019-12-26T20:33:43.561Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "version": "v3", + "lastActivityAt": "2019-12-26T20:33:39.860Z", + "lastActivityUserId": "40051333", + "phases": [ + { + "id": 35, + "name": "Website Design", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-19T00:00:00.000Z", + "duration": 25, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.183Z", + "updatedAt": "2019-12-26T20:33:40.183Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 8, + "products": [ + { + "id": 34, + "name": "Design Iteration (long)", + "projectId": 8, + "directProjectId": null, + "billingAccountId": null, + "templateId": 26, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.336Z", + "updatedAt": "2019-12-26T20:33:40.336Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 35 + } + ] + }, + { + "id": 36, + "name": "Front-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.183Z", + "updatedAt": "2019-12-26T20:33:40.183Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 8, + "products": [ + { + "id": 35, + "name": "Development Iteration (3 Milestones)", + "projectId": 8, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.336Z", + "updatedAt": "2019-12-26T20:33:40.336Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 36 + } + ] + }, + { + "id": 38, + "name": "Back-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.184Z", + "updatedAt": "2019-12-26T20:33:40.184Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 8, + "products": [ + { + "id": 41, + "name": "Development Iteration (3 Milestones)", + "projectId": 8, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.337Z", + "updatedAt": "2019-12-26T20:33:40.337Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 38 + } + ] + }, + { + "id": 40, + "name": "Back-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.184Z", + "updatedAt": "2019-12-26T20:33:40.184Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 8, + "products": [ + { + "id": 42, + "name": "Development Iteration (3 Milestones)", + "projectId": 8, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.343Z", + "updatedAt": "2019-12-26T20:33:40.343Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 40 + } + ] + }, + { + "id": 41, + "name": "Front-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.184Z", + "updatedAt": "2019-12-26T20:33:40.184Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 8, + "products": [ + { + "id": 38, + "name": "Development Iteration (3 Milestones)", + "projectId": 8, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.343Z", + "updatedAt": "2019-12-26T20:33:40.343Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 41 + } + ] + }, + { + "id": 42, + "name": "QA & Bug Fixes", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-18T00:00:00.000Z", + "duration": 24, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.184Z", + "updatedAt": "2019-12-26T20:33:40.185Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 8, + "products": [ + { + "id": 40, + "name": "QA Iteration", + "projectId": 8, + "directProjectId": null, + "billingAccountId": null, + "templateId": 30, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.344Z", + "updatedAt": "2019-12-26T20:33:40.344Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 42 + } + ] + } + ], + "invites": [], + "attachments": [], + "members": [ + { + "id": 9, + "userId": 40051333, + "role": "manager", + "isPrimary": true, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.045Z", + "updatedAt": "2019-12-26T20:33:40.080Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 8 + } + ] + }, + { + "id": 9, + "directProjectId": null, + "billingAccountId": null, + "name": "Reviewed project with copilot as a member with copilot role", + "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message", + "external": null, + "bookmarks": [ + { + "address": "https://connect.topcoder-dev.com/projects/8600/assets21", + "title": "Project 9 link, project audit fields should be used" + }, + { + "createdAt": "2020-01-01T11:10:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets22", + "updatedBy": 2123, + "title": "link 12", + "updatedAt": "2020-02-02T10:11:47.000Z" + }, + { + "createdAt": "2020-01-04T17:12:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets23", + "updatedBy": 2124, + "createdBy": 2124, + "title": "link 13", + "updatedAt": "2020-01-12T17:13:50.199Z" + }, + { + "createdAt": "2020-02-03T10:37:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets24", + "updatedBy": 2125, + "createdBy": 2125, + "title": "link 14", + "updatedAt": "2020-02-04T10:14:47.000Z" + }, + { + "createdAt": "2020-01-05T17:09:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets215", + "createdBy": 2126, + "title": "link 15" + }, + { + "address": "https://connect.topcoder-dev.com/projects/8600/assets216", + "updatedBy": 2127, + "createdBy": 2127, + "title": "link 16", + "updatedAt": "2020-02-08T10:15:47.000Z" + }, + { + "createdAt": "2019-01-09T17:16:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets217", + "updatedBy": 2128, + "createdBy": 2128, + "title": "link 217", + "updatedAt": "2020-01-10T17:17:50.199Z" + }, + { + "createdAt": "2020-02-11T10:18:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets218", + "updatedBy": 2129, + "createdBy": 2129, + "title": "link 218", + "updatedAt": "2020-02-12T10:19:47.000Z" + }, + { + "createdAt": "2020-01-13T17:20:50.198Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets219", + "updatedBy": 2130, + "createdBy": 2130, + "title": "link 219", + "updatedAt": "2020-01-14T17:21:50.199Z" + }, + { + "createdAt": "2020-02-15T10:22:47.000Z", + "address": "https://connect.topcoder-dev.com/projects/8600/assets220", + "title": "link 220", + "updatedAt": "2020-02-16T10:23:47.000Z" + } + ], + "utm": null, + "estimatedPrice": null, + "actualPrice": null, + "terms": [], + "type": "website", + "status": "reviewed", + "details": { + "utm": {}, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" + }, + "users": { + "value": "No one" + } + }, + "hideDiscussions": true + }, + "challengeEligibility": [], + "cancelReason": null, + "templateId": 3, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.049Z", + "updatedAt": "2019-12-26T20:33:43.565Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "version": "v3", + "lastActivityAt": "2019-12-26T20:33:39.870Z", + "lastActivityUserId": "40051333", + "phases": [ + { + "id": 31, + "name": "Front-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.181Z", + "updatedAt": "2019-12-26T20:33:40.181Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 9, + "products": [ + { + "id": 31, + "name": "Development Iteration (3 Milestones)", + "projectId": 9, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.334Z", + "updatedAt": "2019-12-26T20:33:40.334Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 31 + } + ] + }, + { + "id": 32, + "name": "Back-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.182Z", + "updatedAt": "2019-12-26T20:33:40.182Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 9, + "products": [ + { + "id": 32, + "name": "Development Iteration (3 Milestones)", + "projectId": 9, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.334Z", + "updatedAt": "2019-12-26T20:33:40.334Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 32 + } + ] + }, + { + "id": 33, + "name": "Front-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.182Z", + "updatedAt": "2019-12-26T20:33:40.182Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 9, + "products": [ + { + "id": 33, + "name": "Development Iteration (3 Milestones)", + "projectId": 9, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.335Z", + "updatedAt": "2019-12-26T20:33:40.335Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 33 + } + ] + }, + { + "id": 34, + "name": "Back-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.182Z", + "updatedAt": "2019-12-26T20:33:40.182Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 9, + "products": [ + { + "id": 36, + "name": "Development Iteration (3 Milestones)", + "projectId": 9, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.337Z", + "updatedAt": "2019-12-26T20:33:40.337Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 34 + } + ] + }, + { + "id": 37, + "name": "QA & Bug Fixes", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-18T00:00:00.000Z", + "duration": 24, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.183Z", + "updatedAt": "2019-12-26T20:33:40.183Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 9, + "products": [ + { + "id": 37, + "name": "QA Iteration", + "projectId": 9, + "directProjectId": null, + "billingAccountId": null, + "templateId": 30, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.337Z", + "updatedAt": "2019-12-26T20:33:40.337Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 37 + } + ] + }, + { + "id": 39, + "name": "App Design", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-19T00:00:00.000Z", + "duration": 25, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.181Z", + "updatedAt": "2019-12-26T20:33:40.181Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 9, + "products": [ + { + "id": 39, + "name": "Design Iteration (long)", + "projectId": 9, + "directProjectId": null, + "billingAccountId": null, + "templateId": 26, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.338Z", + "updatedAt": "2019-12-26T20:33:40.338Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 39 + } + ] + } + ], + "invites": [], + "attachments": [], + "members": [ + { + "id": 11, + "userId": 40152855, + "role": "copilot", + "isPrimary": false, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:46.751Z", + "updatedAt": "2019-12-26T20:33:46.751Z", + "deletedBy": null, + "createdBy": 40051336, + "updatedBy": 40051336, + "projectId": 9 + }, + { + "id": 8, + "userId": 40051333, + "role": "manager", + "isPrimary": true, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.049Z", + "updatedAt": "2019-12-26T20:33:40.083Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 9 + } + ] + }, + { + "id": 10, + "directProjectId": null, + "billingAccountId": null, + "name": "Reviewed project when copilot is not a member and not invited", + "description": "Hello this is a sample description... This requires at least 160 characters. I'm trying to satisfy this condition. But I could n't if I don't type this unnecessary message", + "external": null, + "bookmarks": [], + "utm": null, + "estimatedPrice": null, + "actualPrice": null, + "terms": [], + "type": "website", + "status": "reviewed", + "details": { + "utm": {}, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" + }, + "users": { + "value": "No one" + } + }, + "hideDiscussions": true + }, + "challengeEligibility": [], + "cancelReason": null, + "templateId": 3, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.050Z", + "updatedAt": "2019-12-26T20:33:43.568Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "version": "v3", + "lastActivityAt": "2019-12-26T20:33:39.869Z", + "lastActivityUserId": "40051333", + "phases": [ + { + "id": 43, + "name": "Front-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.218Z", + "updatedAt": "2019-12-26T20:33:40.218Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 10, + "products": [ + { + "id": 43, + "name": "Development Iteration (3 Milestones)", + "projectId": 10, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.367Z", + "updatedAt": "2019-12-26T20:33:40.367Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 43 + } + ] + }, + { + "id": 44, + "name": "Front-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.217Z", + "updatedAt": "2019-12-26T20:33:40.217Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 10, + "products": [ + { + "id": 48, + "name": "Development Iteration (3 Milestones)", + "projectId": 10, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.370Z", + "updatedAt": "2019-12-26T20:33:40.370Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 44 + } + ] + }, + { + "id": 46, + "name": "QA & Bug Fixes", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-18T00:00:00.000Z", + "duration": 24, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.219Z", + "updatedAt": "2019-12-26T20:33:40.219Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 10, + "products": [ + { + "id": 47, + "name": "QA Iteration", + "projectId": 10, + "directProjectId": null, + "billingAccountId": null, + "templateId": 30, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.369Z", + "updatedAt": "2019-12-26T20:33:40.369Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 46 + } + ] + }, + { + "id": 45, + "name": "Back-End Development, Pt. I", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.218Z", + "updatedAt": "2019-12-26T20:33:40.218Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 10, + "products": [ + { + "id": 46, + "name": "Development Iteration (3 Milestones)", + "projectId": 10, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.369Z", + "updatedAt": "2019-12-26T20:33:40.369Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 45 + } + ] + }, + { + "id": 47, + "name": "Back-End Development, Pt. II", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-13T00:00:00.000Z", + "duration": 19, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.219Z", + "updatedAt": "2019-12-26T20:33:40.219Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 10, + "products": [ + { + "id": 44, + "name": "Development Iteration (3 Milestones)", + "projectId": 10, + "directProjectId": null, + "billingAccountId": null, + "templateId": 27, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.367Z", + "updatedAt": "2019-12-26T20:33:40.367Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 47 + } + ] + }, + { + "id": 48, + "name": "App Design", + "description": null, + "requirements": null, + "status": "draft", + "startDate": "2019-12-26T00:00:00.000Z", + "endDate": "2020-01-19T00:00:00.000Z", + "duration": 25, + "budget": 0, + "spentBudget": 0, + "progress": 0, + "details": {}, + "order": null, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.217Z", + "updatedAt": "2019-12-26T20:33:40.217Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 10, + "products": [ + { + "id": 45, + "name": "Design Iteration (long)", + "projectId": 10, + "directProjectId": null, + "billingAccountId": null, + "templateId": 26, + "type": null, + "estimatedPrice": 0, + "actualPrice": 0, + "details": {}, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.368Z", + "updatedAt": "2019-12-26T20:33:40.368Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "phaseId": 48 + } + ] + } + ], + "invites": [], + "attachments": [], + "members": [ + { + "id": 10, + "userId": 40051333, + "role": "manager", + "isPrimary": true, + "deletedAt": null, + "createdAt": "2019-12-26T20:33:40.050Z", + "updatedAt": "2019-12-26T20:33:40.098Z", + "deletedBy": null, + "createdBy": 40051333, + "updatedBy": 40051333, + "projectId": 10 + } + ] + } + ] diff --git a/migrations/bookmarks/README.md b/migrations/bookmarks/README.md new file mode 100644 index 00000000..eb3d5566 --- /dev/null +++ b/migrations/bookmarks/README.md @@ -0,0 +1,19 @@ +# Migration script for Bookmarks + +To run any of these commands, make sure that `NODE_ENV` environment variable is set to `production` or `development` depend on which environment you would like to run migration for. + +## Migrate the bookmarks to project attachments + +```bash +npm run migrate:bookmarks +``` + +## Revert: migrate project attachments to the bookmarks + +```bash +npm run migrate:bookmarks:revert +``` + +## References + +- [Verification guide for "Migration script for Bookmarks"](Verification.md) \ No newline at end of file diff --git a/migrations/bookmarks/Verification.md b/migrations/bookmarks/Verification.md new file mode 100644 index 00000000..6c5464b9 --- /dev/null +++ b/migrations/bookmarks/Verification.md @@ -0,0 +1,35 @@ +# Verification guide for "Migration script for Bookmarks" + +To check the bookmarks migration, follow the instructions below: + +```bash +-- sync the database (This will drop and re-create the tables) +NODE_ENV=development npm run sync:db +-- Insert the test data +NODE_ENV=development npx babel-node migrations/bookmarks/insertTestData.js +``` + +-- Check the database tables projects and project_attachments using the following SQL statements: + +```sql +select id, name, bookmarks from projects order by id; +select * from project_attachments; +``` + +-- Migrate the bookmarks to project attachments +```bash +NODE_ENV=development npm run migrate:bookmarks +``` + +-- Re-check the database usig the SQL statements above + +-- Revert the migration using the following command : +```bash +NODE_ENV=development npm run migrate:bookmarks:revert +``` + +-- Re-check the database using the following SQL statements: +```sql +select id, name, bookmarks from projects order by id; +select id, pa.type, pa."deletedAt" from project_attachments pa +``` \ No newline at end of file diff --git a/migrations/bookmarks/insertTestData.js b/migrations/bookmarks/insertTestData.js new file mode 100644 index 00000000..aa1e4d27 --- /dev/null +++ b/migrations/bookmarks/insertTestData.js @@ -0,0 +1,46 @@ +/* eslint-disable no-console, no-restricted-syntax, no-await-in-loop */ +/** + * Insert sample projects with bookmarks into the database. + */ + +import models from '../../src/models'; + +const projects = require('./Project.db.dump.json'); + +/** + * Main function. + * + * @returns {Promise} void + */ +async function main() { + for (const project of projects) { + await models.Project.create(project, { + include: [{ + model: models.ProjectMember, + as: 'members', + }, { + model: models.ProjectPhase, + as: 'phases', + include: [{ + model: models.PhaseProduct, + as: 'products', + }], + }, { + model: models.ProjectMemberInvite, + as: 'invites', + }, { + model: models.ProjectAttachment, + as: 'attachments', + }], + }); + console.log(`insert project with id ${project.id}`); + } +} + +main().then(() => { + console.log('done!'); + process.exit(); +}).catch((err) => { + console.error(`Error ${err.name} occurs. Operation failed`); + process.exit(1); +}); diff --git a/migrations/bookmarks/migrateBookmarksToLinks.js b/migrations/bookmarks/migrateBookmarksToLinks.js new file mode 100644 index 00000000..a9ed24bc --- /dev/null +++ b/migrations/bookmarks/migrateBookmarksToLinks.js @@ -0,0 +1,83 @@ +/* eslint-disable no-console, no-restricted-syntax, no-await-in-loop */ +/** + * Migrate project.bookmarks field to project.attachment with attachment type = 'link' + */ + +import _ from 'lodash'; +import sequelize from 'sequelize'; +import models from '../../src/models'; +import { ATTACHMENT_TYPES } from '../../src/constants'; + +console.log('Migrate project.bookmarks to project.attachments for all projects in the database'); + +/** + * Get projects from DB. + * + * @returns {Promise} the DB data + */ +const getProjectsWithBookmarks = () => models.Project.findAll({ + raw: false, + attributes: ['id', 'bookmarks', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy'], + where: sequelize.where( + sequelize.fn('json_array_length', sequelize.col('bookmarks')), + { [sequelize.Op.gt]: 0 }, + ), +}); + +/** + * Executes the bookmarks migration to link attachments + * @returns {Promise} resolved when migration is complete + */ +const migrateBookmarks = async () => { + const projects = await getProjectsWithBookmarks(); + let count = 0; + + console.log(`Found ${projects.length} projects.`); + + for (const project of projects) { + await models.sequelize.transaction(async (tr) => { // eslint-disable-line no-loop-func + count += 1; + const percentage = Math.round((count / projects.length) * 100); + + console.log(`Processing project id ${project.id}: ${count}/${projects.length} (${percentage}%)...`); + + const bookmarks = _.get(project, 'bookmarks', []); + console.log(`Processing project id ${project.id}: found ${bookmarks.length} bookmarks`); + + if (bookmarks.length === 0) { + console.log(`Processing project id ${project.id}: skipped.`); + return; + } + + const attachments = bookmarks.map(b => ({ + projectId: project.id, + type: ATTACHMENT_TYPES.LINK, + title: b.title, + path: b.address, + createdAt: _.isNil(b.createdAt) ? project.createdAt : b.createdAt, + createdBy: _.isNil(b.createdBy) ? project.createdBy : b.createdBy, + updatedAt: _.isNil(b.updatedAt) ? project.updatedAt : b.updatedAt, + updatedBy: _.isNil(b.updatedBy) ? project.updatedBy : b.updatedBy, + tags: [], + })); + + await models.ProjectAttachment.bulkCreate(attachments, { transaction: tr }); + console.log(`Processing project id ${project.id}: attachments created.`); + + project.bookmarks = []; + await project.save({ transaction: tr }); + console.log(`Processing project id ${project.id}: bookmarks removed.`); + + console.log(`Processing project id ${project.id}: done.`); + }); + } +}; + +migrateBookmarks().then(() => { + console.log('Migration of projects bookmarks to project links attachments finished!'); + process.exit(); +}).catch((e) => { + console.error('Migration of projects bookmarks to project links attachments failed!'); + console.error(e); + process.exit(); +}); diff --git a/migrations/bookmarks/migrateLinksToBookmarks.js b/migrations/bookmarks/migrateLinksToBookmarks.js new file mode 100644 index 00000000..4653c557 --- /dev/null +++ b/migrations/bookmarks/migrateLinksToBookmarks.js @@ -0,0 +1,97 @@ +/* eslint-disable no-console, no-restricted-syntax, no-await-in-loop */ +/** + * Migrate project.attachments of type 'link' field to project.bookmark + */ + +import _ from 'lodash'; +import models from '../../src/models'; +import { ATTACHMENT_TYPES } from '../../src/constants'; + +console.log('Migrate project.attachments of type \'link\' to project.bookmarks for all projects in the database'); + +/** + * Get projects from DB. + * + * @returns {Promise} the DB data + */ +const getAllProjectsFromDB = async () => models.Project.findAll({ + raw: false, + attributes: ['id', 'bookmarks'], +}); + +/** + * Gets the active project links (Links that were not deleted) for the given project. + * + * @param {Number} projectId The project id for which to get the active links + * @returns {Promise} The active project links promise + */ +const getActiveProjectLinks = async projectId => models.ProjectAttachment + .findAll({ + where: { + projectId, + type: ATTACHMENT_TYPES.LINK, + }, + raw: true, + }); + +/** + * Executes the migration of link attachments to bookmarks for all projects in the database. + * @returns {Promise} resolved when the migration is complete + */ +const migrateLinksToBookmarks = async () => { + const projects = await getAllProjectsFromDB(); + let count = 0; + + console.log(`Found ${projects.length} projects in total.`); + + for (const project of projects) { + await models.sequelize.transaction(async (tr) => { // eslint-disable-line no-loop-func + count += 1; + const percentage = Math.round((count / projects.length) * 100); + + console.log(`Processing project id ${project.id}: ${count}/${projects.length} (${percentage}%)...`); + + const links = await getActiveProjectLinks(project.id); + console.log(`Processing project id ${project.id}: found ${links.length} link attachments`); + + if (links.length === 0) { + console.log(`Processing project id ${project.id}: skipped.`); + return; + } + + const bookmarks = links.map(link => ({ + title: link.title, + address: link.path, + createdAt: link.createdAt, + createdBy: link.createdBy, + updatedAt: link.updatedAt, + updatedBy: link.updatedBy, + })); + + project.bookmarks = bookmarks; + await project.save({ + transaction: tr, + }); + console.log(`Processing project id ${project.id}: bookmarks created.`); + + await models.ProjectAttachment.destroy({ + where: { + id: _.map(links, 'id'), + }, + transaction: tr, + }); + console.log(`Processing project id ${project.id}: attachments removed.`); + + console.log(`Processing project id ${project.id}: done.`); + }); + } +}; + +migrateLinksToBookmarks().then(() => { + console.log('Migration of projects link attachments to project bookmarks finished!'); + process.exit(); +}).catch((e) => { + console.error('Migration of projects link attachments to project bookmarks failed!'); + console.log(e); + process.exit(); +}); diff --git a/package-lock.json b/package-lock.json index 44dcac10..20326a89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-1.1.2.tgz", "integrity": "sha1-13hAmZ4/fkPnSzsNQzkcFSb3k7g=", "requires": { - "component-type": "^1.2.1", - "join-component": "^1.1.0" + "component-type": "1.2.1", + "join-component": "1.1.0" } }, "@types/bluebird": { @@ -23,8 +23,8 @@ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", "requires": { - "@types/connect": "*", - "@types/node": "*" + "@types/connect": "3.4.33", + "@types/node": "13.7.0" } }, "@types/connect": { @@ -32,7 +32,7 @@ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", "requires": { - "@types/node": "*" + "@types/node": "13.7.0" } }, "@types/express": { @@ -40,9 +40,9 @@ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "*", - "@types/serve-static": "*" + "@types/body-parser": "1.17.1", + "@types/express-serve-static-core": "4.17.2", + "@types/serve-static": "1.13.3" } }, "@types/express-jwt": { @@ -50,8 +50,8 @@ "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", "requires": { - "@types/express": "*", - "@types/express-unless": "*" + "@types/express": "4.17.2", + "@types/express-unless": "0.5.1" } }, "@types/express-serve-static-core": { @@ -59,8 +59,8 @@ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz", "integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==", "requires": { - "@types/node": "*", - "@types/range-parser": "*" + "@types/node": "13.7.0", + "@types/range-parser": "1.2.3" } }, "@types/express-unless": { @@ -68,7 +68,7 @@ "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz", "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==", "requires": { - "@types/express": "*" + "@types/express": "4.17.2" } }, "@types/lodash": { @@ -96,8 +96,8 @@ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", "requires": { - "@types/express-serve-static-core": "*", - "@types/mime": "*" + "@types/express-serve-static-core": "4.17.2", + "@types/mime": "2.0.1" } }, "abbrev": { @@ -111,7 +111,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "mime-types": "~2.1.24", + "mime-types": "2.1.26", "negotiator": "0.6.2" } }, @@ -127,7 +127,7 @@ "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "dev": true, "requires": { - "acorn": "^3.0.4" + "acorn": "3.3.0" }, "dependencies": { "acorn": { @@ -143,7 +143,7 @@ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", "requires": { - "humanize-ms": "^1.2.1" + "humanize-ms": "1.2.1" } }, "ajv": { @@ -151,10 +151,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "3.1.1", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" } }, "ajv-keywords": { @@ -168,12 +168,12 @@ "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.5.tgz", "integrity": "sha512-sWx1hbfHbyKMw6bXOK2k6+lHL8TESWxjAx5hG8fBtT7wcxoXNIsFxZMnFyBjxt3yL14vn7WqBDe5U6BGOadtLg==", "requires": { - "bitsyntax": "~0.1.0", - "bluebird": "^3.5.2", - "buffer-more-ints": "~1.0.0", - "readable-stream": "1.x >=1.1.9", - "safe-buffer": "~5.1.2", - "url-parse": "~1.4.3" + "bitsyntax": "0.1.0", + "bluebird": "3.7.2", + "buffer-more-ints": "1.0.0", + "readable-stream": "1.1.14", + "safe-buffer": "5.1.2", + "url-parse": "1.4.7" } }, "analytics-node": { @@ -181,15 +181,15 @@ "resolved": "https://registry.npmjs.org/analytics-node/-/analytics-node-2.4.1.tgz", "integrity": "sha1-H5bI64h7bEdpEESsf8mhIx+wIPc=", "requires": { - "@segment/loosely-validate-event": "^1.1.2", - "clone": "^2.1.1", - "commander": "^2.9.0", - "crypto-token": "^1.0.1", - "debug": "^2.6.2", - "lodash": "^4.17.4", - "remove-trailing-slash": "^0.1.0", - "superagent": "^3.5.0", - "superagent-retry": "^0.6.0" + "@segment/loosely-validate-event": "1.1.2", + "clone": "2.1.2", + "commander": "2.20.3", + "crypto-token": "1.0.1", + "debug": "2.6.9", + "lodash": "4.17.15", + "remove-trailing-slash": "0.1.0", + "superagent": "3.8.3", + "superagent-retry": "0.6.0" } }, "ansi-align": { @@ -198,7 +198,7 @@ "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "dev": true, "requires": { - "string-width": "^2.0.0" + "string-width": "2.1.1" }, "dependencies": { "ansi-regex": { @@ -219,8 +219,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } }, "strip-ansi": { @@ -229,7 +229,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -268,8 +268,8 @@ "dev": true, "optional": true, "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" + "micromatch": "2.3.11", + "normalize-path": "2.1.1" } }, "app-module-path": { @@ -283,7 +283,7 @@ "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", "dev": true, "requires": { - "default-require-extensions": "^1.0.0" + "default-require-extensions": "1.0.0" } }, "argparse": { @@ -291,7 +291,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { - "sprintf-js": "~1.0.2" + "sprintf-js": "1.0.3" } }, "arr-diff": { @@ -301,7 +301,7 @@ "dev": true, "optional": true, "requires": { - "arr-flatten": "^1.0.1" + "arr-flatten": "1.1.0" } }, "arr-flatten": { @@ -327,9 +327,9 @@ "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" + "define-properties": "1.1.3", + "es-abstract": "1.17.4", + "is-string": "1.0.5" } }, "array-unique": { @@ -345,8 +345,8 @@ "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "define-properties": "1.1.3", + "es-abstract": "1.17.4" } }, "asn1": { @@ -354,7 +354,7 @@ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { - "safer-buffer": "~2.1.0" + "safer-buffer": "2.1.2" } }, "assert-plus": { @@ -391,8 +391,8 @@ "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", "requires": { - "semver": "^5.3.0", - "shimmer": "^1.1.0" + "semver": "5.7.1", + "shimmer": "1.2.1" } }, "asynckit": { @@ -411,13 +411,13 @@ "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.12.2.tgz", "integrity": "sha512-0VfPu5UcgkGKQc7Q8KPqgkqqhLgXGsDCro2tde7hHPYK9JEzOyq82v0szUTHWlwQE1VT8K2/qZAsGDf7hFjI7g==", "requires": { - "base64-js": "^1.3.0", - "idtoken-verifier": "^2.0.1", - "js-cookie": "^2.2.0", - "qs": "^6.7.0", - "superagent": "^3.8.3", - "url-join": "^4.0.1", - "winchan": "^0.2.2" + "base64-js": "1.3.1", + "idtoken-verifier": "2.0.1", + "js-cookie": "2.2.1", + "qs": "6.9.1", + "superagent": "3.8.3", + "url-join": "4.0.1", + "winchan": "0.2.2" } }, "aws-sdk": { @@ -460,21 +460,21 @@ "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", "dev": true, "requires": { - "babel-core": "^6.26.0", - "babel-polyfill": "^6.26.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "chokidar": "^1.6.1", - "commander": "^2.11.0", - "convert-source-map": "^1.5.0", - "fs-readdir-recursive": "^1.0.0", - "glob": "^7.1.2", - "lodash": "^4.17.4", - "output-file-sync": "^1.1.2", - "path-is-absolute": "^1.0.1", - "slash": "^1.0.0", - "source-map": "^0.5.6", - "v8flags": "^2.1.1" + "babel-core": "6.26.3", + "babel-polyfill": "6.26.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "chokidar": "1.7.0", + "commander": "2.20.3", + "convert-source-map": "1.7.0", + "fs-readdir-recursive": "1.1.0", + "glob": "7.1.6", + "lodash": "4.17.15", + "output-file-sync": "1.1.2", + "path-is-absolute": "1.0.1", + "slash": "1.0.0", + "source-map": "0.5.7", + "v8flags": "2.1.1" }, "dependencies": { "babel-runtime": { @@ -483,8 +483,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } }, "glob": { @@ -493,12 +493,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "source-map": { @@ -515,9 +515,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "chalk": "1.1.3", + "esutils": "2.0.3", + "js-tokens": "3.0.2" } }, "babel-core": { @@ -526,25 +526,25 @@ "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.7.0", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.15", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" }, "dependencies": { "babel-runtime": { @@ -553,8 +553,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } }, "json5": { @@ -577,10 +577,10 @@ "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", - "babel-traverse": "^6.23.1", - "babel-types": "^6.23.0", - "babylon": "^6.17.0" + "babel-code-frame": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0" } }, "babel-generator": { @@ -589,14 +589,14 @@ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.15", + "source-map": "0.5.7", + "trim-right": "1.0.1" }, "dependencies": { "babel-runtime": { @@ -605,8 +605,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } }, "source-map": { @@ -623,10 +623,10 @@ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -635,8 +635,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -647,10 +647,10 @@ "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.15" }, "dependencies": { "babel-runtime": { @@ -659,8 +659,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -671,11 +671,11 @@ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "dev": true, "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -684,8 +684,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -696,8 +696,8 @@ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -706,8 +706,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -718,8 +718,8 @@ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -728,8 +728,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -740,8 +740,8 @@ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -750,8 +750,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -762,9 +762,9 @@ "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.15" }, "dependencies": { "babel-runtime": { @@ -773,8 +773,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -785,12 +785,12 @@ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", "dev": true, "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-optimise-call-expression": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -799,8 +799,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -811,8 +811,8 @@ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -821,8 +821,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -833,7 +833,7 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -842,8 +842,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -860,7 +860,7 @@ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -869,8 +869,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -881,7 +881,7 @@ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -890,8 +890,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -902,7 +902,7 @@ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -911,8 +911,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -923,11 +923,11 @@ "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "lodash": "4.17.15" }, "dependencies": { "babel-runtime": { @@ -936,8 +936,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -948,15 +948,15 @@ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-define-map": "6.26.0", + "babel-helper-function-name": "6.24.1", + "babel-helper-optimise-call-expression": "6.24.1", + "babel-helper-replace-supers": "6.24.1", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -965,8 +965,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -977,8 +977,8 @@ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -987,8 +987,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -999,7 +999,7 @@ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1008,8 +1008,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1020,8 +1020,8 @@ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1030,8 +1030,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1042,7 +1042,7 @@ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1051,8 +1051,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1063,9 +1063,9 @@ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1074,8 +1074,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1086,7 +1086,7 @@ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1095,8 +1095,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1107,9 +1107,9 @@ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1118,8 +1118,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1130,10 +1130,10 @@ "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", "dev": true, "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" + "babel-plugin-transform-strict-mode": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1142,8 +1142,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1154,9 +1154,9 @@ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", "dev": true, "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-helper-hoist-variables": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1165,8 +1165,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1177,9 +1177,9 @@ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1188,8 +1188,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1200,8 +1200,8 @@ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", "dev": true, "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" + "babel-helper-replace-supers": "6.24.1", + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1210,8 +1210,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1222,12 +1222,12 @@ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", "dev": true, "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "babel-helper-call-delegate": "6.24.1", + "babel-helper-get-function-arity": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1236,8 +1236,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1248,8 +1248,8 @@ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1258,8 +1258,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1270,7 +1270,7 @@ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1279,8 +1279,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1291,9 +1291,9 @@ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", "dev": true, "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1302,8 +1302,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1314,7 +1314,7 @@ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1323,8 +1323,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1335,7 +1335,7 @@ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1344,8 +1344,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1356,9 +1356,9 @@ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", "dev": true, "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" + "babel-helper-regex": "6.26.0", + "babel-runtime": "6.26.0", + "regexpu-core": "2.0.0" }, "dependencies": { "babel-runtime": { @@ -1367,8 +1367,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1379,7 +1379,7 @@ "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", "dev": true, "requires": { - "regenerator-transform": "^0.10.0" + "regenerator-transform": "0.10.1" } }, "babel-plugin-transform-runtime": { @@ -1388,7 +1388,7 @@ "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1397,8 +1397,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1409,8 +1409,8 @@ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1419,8 +1419,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1431,9 +1431,9 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" + "babel-runtime": "6.26.0", + "core-js": "2.6.11", + "regenerator-runtime": "0.10.5" }, "dependencies": { "babel-runtime": { @@ -1442,8 +1442,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" }, "dependencies": { "regenerator-runtime": { @@ -1468,30 +1468,30 @@ "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", "dev": true, "requires": { - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.24.1", - "babel-plugin-transform-es2015-classes": "^6.24.1", - "babel-plugin-transform-es2015-computed-properties": "^6.24.1", - "babel-plugin-transform-es2015-destructuring": "^6.22.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", - "babel-plugin-transform-es2015-for-of": "^6.22.0", - "babel-plugin-transform-es2015-function-name": "^6.24.1", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", - "babel-plugin-transform-es2015-modules-umd": "^6.24.1", - "babel-plugin-transform-es2015-object-super": "^6.24.1", - "babel-plugin-transform-es2015-parameters": "^6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", - "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", - "babel-plugin-transform-regenerator": "^6.24.1" + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", + "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", + "babel-plugin-transform-es2015-modules-umd": "6.24.1", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "babel-plugin-transform-regenerator": "6.26.0" } }, "babel-register": { @@ -1500,13 +1500,13 @@ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" + "babel-core": "6.26.3", + "babel-runtime": "6.26.0", + "core-js": "2.6.11", + "home-or-tmp": "2.0.0", + "lodash": "4.17.15", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18" }, "dependencies": { "babel-runtime": { @@ -1515,8 +1515,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1526,7 +1526,7 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz", "integrity": "sha1-eIuUtvY04luRvWxd9y1GdFevsAA=", "requires": { - "core-js": "^2.1.0" + "core-js": "2.6.11" } }, "babel-template": { @@ -1535,11 +1535,11 @@ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.15" }, "dependencies": { "babel-runtime": { @@ -1548,8 +1548,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1560,15 +1560,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.4", + "lodash": "4.17.15" }, "dependencies": { "babel-runtime": { @@ -1577,8 +1577,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1589,10 +1589,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "babel-runtime": "6.26.0", + "esutils": "2.0.3", + "lodash": "4.17.15", + "to-fast-properties": "1.0.3" }, "dependencies": { "babel-runtime": { @@ -1601,8 +1601,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -1618,7 +1618,7 @@ "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", "requires": { - "precond": "0.2" + "precond": "0.2.3" } }, "balanced-match": { @@ -1632,13 +1632,13 @@ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.3.0", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.2", + "pascalcase": "0.1.1" }, "dependencies": { "define-property": { @@ -1647,7 +1647,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -1656,7 +1656,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -1665,7 +1665,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -1674,9 +1674,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.3" } }, "isobject": { @@ -1703,7 +1703,7 @@ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "bin-protocol": { @@ -1711,9 +1711,9 @@ "resolved": "https://registry.npmjs.org/bin-protocol/-/bin-protocol-3.1.1.tgz", "integrity": "sha512-9vCGfaHC2GBHZwGQdG+DpyXfmLvx9uKtf570wMLwIc9wmTIDgsdCBXQxTZu5X2GyogkfBks2Ode4N0sUVxJ2qQ==", "requires": { - "lodash": "^4.17.11", - "long": "^4.0.0", - "protocol-buffers-schema": "^3.0.0" + "lodash": "4.17.15", + "long": "4.0.0", + "protocol-buffers-schema": "3.4.0" } }, "binary-extensions": { @@ -1735,9 +1735,9 @@ "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", "requires": { - "buffer-more-ints": "~1.0.0", - "debug": "~2.6.9", - "safe-buffer": "~5.1.2" + "buffer-more-ints": "1.0.0", + "debug": "2.6.9", + "safe-buffer": "5.1.2" } }, "bluebird": { @@ -1751,15 +1751,15 @@ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "requires": { "bytes": "3.1.0", - "content-type": "~1.0.4", + "content-type": "1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "1.1.2", "http-errors": "1.7.2", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", + "on-finished": "2.3.0", "qs": "6.7.0", "raw-body": "2.4.0", - "type-is": "~1.6.17" + "type-is": "1.6.18" }, "dependencies": { "qs": { @@ -1775,13 +1775,13 @@ "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", "dev": true, "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" + "ansi-align": "2.0.0", + "camelcase": "4.1.0", + "chalk": "2.4.2", + "cli-boxes": "1.0.0", + "string-width": "2.1.1", + "term-size": "1.2.0", + "widest-line": "2.0.1" }, "dependencies": { "ansi-regex": { @@ -1796,7 +1796,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "camelcase": { @@ -1811,9 +1811,9 @@ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "has-flag": { @@ -1834,8 +1834,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } }, "strip-ansi": { @@ -1844,7 +1844,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "supports-color": { @@ -1853,7 +1853,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -1863,7 +1863,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -1874,9 +1874,9 @@ "dev": true, "optional": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.3" } }, "browser-stdout": { @@ -1890,9 +1890,9 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "base64-js": "1.3.1", + "ieee754": "1.1.13", + "isarray": "1.0.0" }, "dependencies": { "isarray": { @@ -1933,10 +1933,10 @@ "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", "requires": { - "dtrace-provider": "~0.8", - "moment": "^2.10.6", - "mv": "~2", - "safe-json-stringify": "~1" + "dtrace-provider": "0.8.8", + "moment": "2.24.0", + "mv": "2.1.1", + "safe-json-stringify": "1.2.0" } }, "bytes": { @@ -1950,15 +1950,15 @@ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "collection-visit": "1.0.0", + "component-emitter": "1.3.0", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.1", + "to-object-path": "0.3.0", + "union-value": "1.0.1", + "unset-value": "1.0.0" }, "dependencies": { "isobject": { @@ -1975,7 +1975,7 @@ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "^0.2.0" + "callsites": "0.2.0" } }, "callsites": { @@ -2007,9 +2007,9 @@ "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", "dev": true, "requires": { - "assertion-error": "^1.0.1", - "deep-eql": "^0.1.3", - "type-detect": "^1.0.0" + "assertion-error": "1.1.0", + "deep-eql": "0.1.3", + "type-detect": "1.0.0" } }, "chai-as-promised": { @@ -2018,7 +2018,7 @@ "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, "requires": { - "check-error": "^1.0.2" + "check-error": "1.0.2" } }, "chalk": { @@ -2026,11 +2026,11 @@ "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" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "check-error": { @@ -2052,15 +2052,14 @@ "dev": true, "optional": true, "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" + "anymatch": "1.3.2", + "async-each": "1.0.3", + "glob-parent": "2.0.0", + "inherits": "2.0.4", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1" } }, "ci-info": { @@ -2081,10 +2080,10 @@ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" }, "dependencies": { "define-property": { @@ -2093,7 +2092,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "isobject": { @@ -2116,12 +2115,12 @@ "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", "dev": true, "requires": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" + "ansi-regex": "2.1.1", + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-iterator": "2.0.3", + "memoizee": "0.4.14", + "timers-ext": "0.1.7" } }, "cli-cursor": { @@ -2130,7 +2129,7 @@ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "dev": true, "requires": { - "restore-cursor": "^1.0.1" + "restore-cursor": "1.0.1" } }, "cli-width": { @@ -2145,9 +2144,9 @@ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "string-width": "3.1.0", + "strip-ansi": "5.2.0", + "wrap-ansi": "5.1.0" }, "dependencies": { "ansi-regex": { @@ -2168,9 +2167,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" } }, "strip-ansi": { @@ -2179,7 +2178,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "4.1.0" } } } @@ -2194,8 +2193,8 @@ "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", "requires": { - "is-bluebird": "^1.0.2", - "shimmer": "^1.1.0" + "is-bluebird": "1.0.2", + "shimmer": "1.2.1" } }, "co": { @@ -2230,15 +2229,14 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "map-visit": "1.0.0", + "object-visit": "1.0.1" } }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -2246,8 +2244,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.4.0", @@ -2259,7 +2256,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "commander": { @@ -2282,7 +2279,7 @@ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "requires": { - "mime-db": ">= 1.43.0 < 2" + "mime-db": "1.43.0" } }, "compression": { @@ -2290,13 +2287,13 @@ "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", "requires": { - "accepts": "~1.3.5", + "accepts": "1.3.7", "bytes": "3.0.0", - "compressible": "~2.0.16", + "compressible": "2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", + "on-headers": "1.0.2", "safe-buffer": "5.1.2", - "vary": "~1.1.2" + "vary": "1.1.2" }, "dependencies": { "bytes": { @@ -2317,10 +2314,10 @@ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "buffer-from": "1.1.1", + "inherits": "2.0.4", + "readable-stream": "2.3.7", + "typedarray": "0.0.6" }, "dependencies": { "isarray": { @@ -2335,13 +2332,13 @@ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -2350,7 +2347,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -2360,7 +2357,7 @@ "resolved": "https://registry.npmjs.org/config/-/config-1.31.0.tgz", "integrity": "sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA==", "requires": { - "json5": "^1.0.1" + "json5": "1.0.1" } }, "config-chain": { @@ -2369,8 +2366,8 @@ "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "dev": true, "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "ini": "1.3.5", + "proto-list": "1.2.4" } }, "configstore": { @@ -2379,12 +2376,12 @@ "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", "dev": true, "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" + "dot-prop": "4.2.0", + "graceful-fs": "4.2.3", + "make-dir": "1.3.0", + "unique-string": "1.0.0", + "write-file-atomic": "2.4.3", + "xdg-basedir": "3.0.0" } }, "connection-parse": { @@ -2416,8 +2413,8 @@ "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", "requires": { - "async-listener": "^0.6.0", - "emitter-listener": "^1.1.1" + "async-listener": "0.6.10", + "emitter-listener": "1.1.2" } }, "convert-source-map": { @@ -2426,7 +2423,7 @@ "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { - "safe-buffer": "~5.1.1" + "safe-buffer": "5.1.2" } }, "cookie": { @@ -2465,8 +2462,8 @@ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "requires": { - "object-assign": "^4", - "vary": "^1" + "object-assign": "4.1.1", + "vary": "1.1.2" } }, "create-error-class": { @@ -2475,7 +2472,7 @@ "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "dev": true, "requires": { - "capture-stack-trace": "^1.0.0" + "capture-stack-trace": "1.0.1" } }, "cross-spawn": { @@ -2484,9 +2481,9 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "lru-cache": "4.0.2", + "shebang-command": "1.2.0", + "which": "1.3.1" } }, "crypto-js": { @@ -2511,8 +2508,8 @@ "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", "dev": true, "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "es5-ext": "0.10.53", + "type": "1.2.0" } }, "dashdash": { @@ -2520,7 +2517,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "debug": { @@ -2577,7 +2574,7 @@ "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", "dev": true, "requires": { - "strip-bom": "^2.0.0" + "strip-bom": "2.0.0" }, "dependencies": { "strip-bom": { @@ -2586,7 +2583,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } } } @@ -2597,7 +2594,7 @@ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "object-keys": "1.1.1" } }, "define-property": { @@ -2606,8 +2603,8 @@ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "is-descriptor": "1.0.2", + "isobject": "3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -2616,7 +2613,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -2625,7 +2622,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -2634,9 +2631,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.3" } }, "isobject": { @@ -2674,7 +2671,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "^2.0.0" + "repeating": "2.0.1" } }, "diff": { @@ -2683,13 +2680,18 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "diff-match-patch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz", + "integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==" + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "^2.0.2" + "esutils": "2.0.3" } }, "dot-prop": { @@ -2698,7 +2700,7 @@ "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "dev": true, "requires": { - "is-obj": "^1.0.0" + "is-obj": "1.0.1" } }, "dottie": { @@ -2712,7 +2714,7 @@ "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", "optional": true, "requires": { - "nan": "^2.14.0" + "nan": "2.14.0" } }, "duplexer3": { @@ -2726,8 +2728,8 @@ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" }, "dependencies": { "jsbn": { @@ -2742,7 +2744,7 @@ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "editorconfig": { @@ -2751,10 +2753,10 @@ "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", "dev": true, "requires": { - "commander": "^2.19.0", - "lru-cache": "^4.1.5", - "semver": "^5.6.0", - "sigmund": "^1.0.1" + "commander": "2.20.3", + "lru-cache": "4.1.5", + "semver": "5.7.1", + "sigmund": "1.0.1" }, "dependencies": { "lru-cache": { @@ -2763,8 +2765,8 @@ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } } } @@ -2779,9 +2781,9 @@ "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-16.6.0.tgz", "integrity": "sha512-MhsdE2JaBJoV1EGzSkCqqhNGxafXJuhPr+eD3vbXmsk/QWhaiU12oyXF0VhjcL8+UlwTHv0CAUbyjtE1wqoIdw==", "requires": { - "agentkeepalive": "^3.4.1", - "chalk": "^1.0.0", - "lodash": "^4.17.10" + "agentkeepalive": "3.5.2", + "chalk": "1.1.3", + "lodash": "4.17.15" } }, "emitter-listener": { @@ -2789,7 +2791,7 @@ "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" + "shimmer": "1.2.1" } }, "emoji-regex": { @@ -2809,7 +2811,7 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "is-arrayish": "0.2.1" } }, "es-abstract": { @@ -2818,17 +2820,17 @@ "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", "dev": true, "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.1.5", + "is-regex": "1.0.5", + "object-inspect": "1.7.0", + "object-keys": "1.1.1", + "object.assign": "4.1.0", + "string.prototype.trimleft": "2.1.1", + "string.prototype.trimright": "2.1.1" } }, "es-to-primitive": { @@ -2837,9 +2839,9 @@ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "1.1.5", + "is-date-object": "1.0.2", + "is-symbol": "1.0.3" } }, "es5-ext": { @@ -2848,9 +2850,9 @@ "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dev": true, "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.3", + "next-tick": "1.0.0" } }, "es6-iterator": { @@ -2859,9 +2861,9 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-symbol": "3.1.3" } }, "es6-map": { @@ -2870,12 +2872,12 @@ "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.3", + "event-emitter": "0.3.5" } }, "es6-promise": { @@ -2889,11 +2891,11 @@ "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-iterator": "2.0.3", "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" + "event-emitter": "0.3.5" }, "dependencies": { "es6-symbol": { @@ -2902,8 +2904,8 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.1", + "es5-ext": "0.10.53" } } } @@ -2914,8 +2916,8 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" + "d": "1.0.1", + "ext": "1.4.0" } }, "es6-weak-map": { @@ -2924,10 +2926,10 @@ "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.3" } }, "escape-html": { @@ -2945,11 +2947,11 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.13.0.tgz", "integrity": "sha512-eYk2dCkxR07DsHA/X2hRBj0CFAZeri/LyDMc0C8JT1Hqi6JnVpMhJ7XFITbb0+yZS3lVkaPL2oCkZ3AVmeVbMw==", "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "esprima": "4.0.1", + "estraverse": "4.3.0", + "esutils": "2.0.3", + "optionator": "0.8.3", + "source-map": "0.6.1" }, "dependencies": { "esprima": { @@ -2965,10 +2967,10 @@ "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", "dev": true, "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "es6-map": "0.1.5", + "es6-weak-map": "2.0.3", + "esrecurse": "4.2.1", + "estraverse": "4.3.0" } }, "eslint": { @@ -2977,41 +2979,41 @@ "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", "dev": true, "requires": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.0.0" + "babel-code-frame": "6.26.0", + "chalk": "1.1.3", + "concat-stream": "1.6.2", + "debug": "2.6.9", + "doctrine": "2.1.0", + "escope": "3.6.0", + "espree": "3.5.4", + "esquery": "1.0.1", + "estraverse": "4.3.0", + "esutils": "2.0.3", + "file-entry-cache": "2.0.0", + "glob": "7.1.6", + "globals": "9.18.0", + "ignore": "3.3.10", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.20.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.13.1", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.15", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.3", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.7.8", + "strip-bom": "3.0.0", + "strip-json-comments": "2.0.1", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" }, "dependencies": { "glob": { @@ -3020,12 +3022,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "user-home": { @@ -3034,7 +3036,7 @@ "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", "dev": true, "requires": { - "os-homedir": "^1.0.0" + "os-homedir": "1.0.2" } } } @@ -3045,7 +3047,7 @@ "integrity": "sha512-/fhjt/VqzBA2SRsx7ErDtv6Ayf+XLw9LIOqmpBuHFCVwyJo2EtzGWMB9fYRFBoWWQLxmNmCpenNiH0RxyeS41w==", "dev": true, "requires": { - "eslint-restricted-globals": "^0.1.1" + "eslint-restricted-globals": "0.1.1" } }, "eslint-import-resolver-node": { @@ -3054,8 +3056,8 @@ "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", "dev": true, "requires": { - "debug": "^2.6.9", - "resolve": "^1.13.1" + "debug": "2.6.9", + "resolve": "1.15.0" } }, "eslint-module-utils": { @@ -3064,8 +3066,8 @@ "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", "dev": true, "requires": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" + "debug": "2.6.9", + "pkg-dir": "2.0.0" } }, "eslint-plugin-import": { @@ -3074,18 +3076,18 @@ "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", "dev": true, "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", - "contains-path": "^0.1.0", - "debug": "^2.6.9", + "array-includes": "3.1.1", + "array.prototype.flat": "1.2.3", + "contains-path": "0.1.0", + "debug": "2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", - "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.0", - "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" + "eslint-import-resolver-node": "0.3.3", + "eslint-module-utils": "2.5.2", + "has": "1.0.3", + "minimatch": "3.0.4", + "object.values": "1.1.1", + "read-pkg-up": "2.0.0", + "resolve": "1.15.0" }, "dependencies": { "doctrine": { @@ -3094,8 +3096,8 @@ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "esutils": "2.0.3", + "isarray": "1.0.0" } }, "isarray": { @@ -3118,8 +3120,8 @@ "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "dev": true, "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" + "acorn": "5.7.3", + "acorn-jsx": "3.0.1" } }, "esprima": { @@ -3133,7 +3135,7 @@ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "4.3.0" } }, "esrecurse": { @@ -3142,7 +3144,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "4.3.0" } }, "estraverse": { @@ -3166,8 +3168,8 @@ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.1", + "es5-ext": "0.10.53" } }, "events": { @@ -3181,13 +3183,13 @@ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" } }, "exit-hook": { @@ -3203,7 +3205,7 @@ "dev": true, "optional": true, "requires": { - "is-posix-bracket": "^0.1.0" + "is-posix-bracket": "0.1.1" } }, "expand-range": { @@ -3213,7 +3215,7 @@ "dev": true, "optional": true, "requires": { - "fill-range": "^2.1.0" + "fill-range": "2.2.4" } }, "express": { @@ -3221,36 +3223,36 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { - "accepts": "~1.3.7", + "accepts": "1.3.7", "array-flatten": "1.1.1", "body-parser": "1.19.0", "content-disposition": "0.5.3", - "content-type": "~1.0.4", + "content-type": "1.0.4", "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", + "proxy-addr": "2.0.5", "qs": "6.7.0", - "range-parser": "~1.2.1", + "range-parser": "1.2.1", "safe-buffer": "5.1.2", "send": "0.17.1", "serve-static": "1.14.1", "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", + "statuses": "1.5.0", + "type-is": "1.6.18", "utils-merge": "1.0.1", - "vary": "~1.1.2" + "vary": "1.1.2" }, "dependencies": { "qs": { @@ -3265,8 +3267,8 @@ "resolved": "https://registry.npmjs.org/express-list-routes/-/express-list-routes-0.1.4.tgz", "integrity": "sha1-xlwxw/thnHnAVD97TsToMFbs5hY=", "requires": { - "colors": "^1.0.3", - "lodash": "^3.0.0" + "colors": "1.4.0", + "lodash": "3.10.1" }, "dependencies": { "lodash": { @@ -3281,7 +3283,7 @@ "resolved": "https://registry.npmjs.org/express-request-id/-/express-request-id-1.4.1.tgz", "integrity": "sha512-qpxK6XhDYtdx9FvxwCHkUeZVWtkGbWR87hBAzGECfwYF/QQCPXEwwB2/9NGkOR1tT7/aLs9mma3CT0vjSzuZVw==", "requires": { - "uuid": "^3.3.2" + "uuid": "3.3.2" } }, "express-sanitizer": { @@ -3298,7 +3300,7 @@ "resolved": "https://registry.npmjs.org/express-validation/-/express-validation-0.6.0.tgz", "integrity": "sha1-DXf0r8flixIBat7FmzJb7v2dwmg=", "requires": { - "lodash": "^4.9.0" + "lodash": "4.17.15" } }, "ext": { @@ -3307,7 +3309,7 @@ "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", "dev": true, "requires": { - "type": "^2.0.0" + "type": "2.0.0" }, "dependencies": { "type": { @@ -3329,8 +3331,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -3339,7 +3341,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -3351,7 +3353,7 @@ "dev": true, "optional": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "extsprintf": { @@ -3380,8 +3382,8 @@ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" } }, "file-entry-cache": { @@ -3390,8 +3392,8 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "flat-cache": "1.3.4", + "object-assign": "4.1.1" } }, "file-uri-to-path": { @@ -3412,8 +3414,8 @@ "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", "dev": true, "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" + "glob": "7.1.6", + "minimatch": "3.0.4" }, "dependencies": { "glob": { @@ -3422,12 +3424,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -3439,11 +3441,11 @@ "dev": true, "optional": true, "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "3.1.1", + "repeat-element": "1.1.3", + "repeat-string": "1.6.1" } }, "finalhandler": { @@ -3452,12 +3454,12 @@ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.3", + "statuses": "1.5.0", + "unpipe": "1.0.0" } }, "find-up": { @@ -3466,7 +3468,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } }, "flat": { @@ -3475,7 +3477,7 @@ "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "dev": true, "requires": { - "is-buffer": "~2.0.3" + "is-buffer": "2.0.4" }, "dependencies": { "is-buffer": { @@ -3492,10 +3494,10 @@ "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "circular-json": "0.3.3", + "graceful-fs": "4.2.3", + "rimraf": "2.6.3", + "write": "0.2.1" }, "dependencies": { "glob": { @@ -3504,12 +3506,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "rimraf": { @@ -3518,7 +3520,7 @@ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.6" } } } @@ -3528,7 +3530,7 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "requires": { - "debug": "=3.1.0" + "debug": "3.1.0" }, "dependencies": { "debug": { @@ -3554,7 +3556,7 @@ "dev": true, "optional": true, "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } }, "forever-agent": { @@ -3567,9 +3569,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "asynckit": "0.4.0", + "combined-stream": "1.0.8", + "mime-types": "2.1.26" } }, "formatio": { @@ -3578,7 +3580,7 @@ "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", "dev": true, "requires": { - "samsam": "~1.1" + "samsam": "1.1.2" } }, "formidable": { @@ -3597,7 +3599,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "^0.2.2" + "map-cache": "0.2.2" } }, "fresh": { @@ -3611,9 +3613,9 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "4.2.3", + "jsonfile": "4.0.0", + "universalify": "0.1.2" } }, "fs-readdir-recursive": { @@ -3627,564 +3629,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", - "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "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" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "dev": true, - "optional": true - } - } - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4197,7 +3641,7 @@ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "dev": true, "requires": { - "is-property": "^1.0.2" + "is-property": "1.0.2" } }, "generate-object-property": { @@ -4206,7 +3650,7 @@ "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", "dev": true, "requires": { - "is-property": "^1.0.0" + "is-property": "1.0.2" } }, "get-caller-file": { @@ -4232,7 +3676,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "glob": { @@ -4241,11 +3685,11 @@ "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", "optional": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-base": { @@ -4255,8 +3699,8 @@ "dev": true, "optional": true, "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" + "glob-parent": "2.0.0", + "is-glob": "2.0.1" } }, "glob-parent": { @@ -4264,9 +3708,8 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, - "optional": true, "requires": { - "is-glob": "^2.0.0" + "is-glob": "2.0.1" } }, "global-dirs": { @@ -4275,7 +3718,7 @@ "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "dev": true, "requires": { - "ini": "^1.3.4" + "ini": "1.3.5" } }, "globals": { @@ -4290,17 +3733,17 @@ "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" + "create-error-class": "3.0.2", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.2.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.1", + "safe-buffer": "5.1.2", + "timed-out": "4.0.1", + "unzip-response": "2.0.1", + "url-parse-lax": "1.0.0" } }, "graceful-fs": { @@ -4319,12 +3762,11 @@ "version": "4.7.2", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.2.tgz", "integrity": "sha512-4PwqDL2laXtTWZghzzCtunQUTLbo31pcCJrd/B/9JP8XbhVzpS5ZXuKqlOzsd1rtcaLo4KqAn8nl8mkknS4MHw==", - "dev": true, "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" + "neo-async": "2.6.1", + "optimist": "0.6.1", + "source-map": "0.6.1", + "uglify-js": "3.7.7" } }, "har-schema": { @@ -4337,8 +3779,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "ajv": "6.11.0", + "har-schema": "2.0.0" } }, "has": { @@ -4347,7 +3789,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "^1.1.1" + "function-bind": "1.1.1" } }, "has-ansi": { @@ -4355,7 +3797,7 @@ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "has-flag": { @@ -4376,9 +3818,9 @@ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -4395,8 +3837,8 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "is-number": "3.0.0", + "kind-of": "4.0.0" }, "dependencies": { "is-number": { @@ -4405,7 +3847,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -4414,7 +3856,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -4425,7 +3867,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -4435,8 +3877,8 @@ "resolved": "https://registry.npmjs.org/hashring/-/hashring-3.2.0.tgz", "integrity": "sha1-/aTv3oqiLNuX+x0qZeiEAeHBRM4=", "requires": { - "connection-parse": "0.0.x", - "simple-lru-cache": "0.0.x" + "connection-parse": "0.0.7", + "simple-lru-cache": "0.0.2" } }, "he": { @@ -4456,8 +3898,8 @@ "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "hosted-git-info": { @@ -4476,10 +3918,10 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { - "depd": "~1.1.2", + "depd": "1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", + "statuses": "1.5.0", "toidentifier": "1.0.0" }, "dependencies": { @@ -4495,9 +3937,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.16.1" } }, "humanize-ms": { @@ -4505,7 +3947,7 @@ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", "requires": { - "ms": "^2.0.0" + "ms": "2.0.0" } }, "iconv-lite": { @@ -4513,7 +3955,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } }, "idtoken-verifier": { @@ -4521,12 +3963,12 @@ "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-2.0.1.tgz", "integrity": "sha512-sLLFPPc6D6Ske7JNHHrrWHbQKuY1OJN9GcJd6Y1LjMvInJBr26Axbo6o07JYPPTRUzJahBWHudabgFoNo23lMw==", "requires": { - "base64-js": "^1.3.0", - "crypto-js": "^3.1.9-1", - "es6-promise": "^4.2.8", - "jsbn": "^1.1.0", - "unfetch": "^4.1.0", - "url-join": "^4.0.1" + "base64-js": "1.3.1", + "crypto-js": "3.1.9-1", + "es6-promise": "4.2.8", + "jsbn": "1.1.0", + "unfetch": "4.1.0", + "url-join": "4.0.1" } }, "ieee754": { @@ -4568,8 +4010,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -4589,19 +4031,19 @@ "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", "dev": true, "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.2.0", + "figures": "1.7.0", + "lodash": "4.17.15", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" } }, "interpret": { @@ -4616,7 +4058,7 @@ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.4.0" } }, "ipaddr.js": { @@ -4630,7 +4072,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-arguments": { @@ -4651,7 +4093,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "1.13.1" } }, "is-bluebird": { @@ -4677,7 +4119,7 @@ "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { - "ci-info": "^1.5.0" + "ci-info": "1.6.0" } }, "is-data-descriptor": { @@ -4686,7 +4128,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-date-object": { @@ -4701,9 +4143,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" }, "dependencies": { "kind-of": { @@ -4728,7 +4170,7 @@ "dev": true, "optional": true, "requires": { - "is-primitive": "^2.0.0" + "is-primitive": "2.0.0" } }, "is-extendable": { @@ -4741,8 +4183,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true, - "optional": true + "dev": true }, "is-finite": { "version": "1.0.2", @@ -4750,7 +4191,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-fullwidth-code-point": { @@ -4759,7 +4200,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-generator-function": { @@ -4773,9 +4214,8 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, - "optional": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "is-installed-globally": { @@ -4784,8 +4224,8 @@ "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "dev": true, "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" + "global-dirs": "0.1.1", + "is-path-inside": "1.0.1" } }, "is-my-ip-valid": { @@ -4800,11 +4240,11 @@ "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", "dev": true, "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" + "generate-function": "2.3.1", + "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.2" } }, "is-npm": { @@ -4820,7 +4260,7 @@ "dev": true, "optional": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-obj": { @@ -4835,7 +4275,7 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "1.0.2" } }, "is-plain-object": { @@ -4844,7 +4284,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -4893,7 +4333,7 @@ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "dev": true, "requires": { - "has": "^1.0.3" + "has": "1.0.3" } }, "is-resolvable": { @@ -4926,7 +4366,7 @@ "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "^1.0.1" + "has-symbols": "1.0.1" } }, "is-typedarray": { @@ -4992,14 +4432,14 @@ "integrity": "sha1-eBeVZWAYohdMX2DzZ+5dNhy1e3c=", "dev": true, "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "istanbul-api": "^1.1.0-alpha", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "which": "^1.1.1", - "wordwrap": "^1.0.0" + "abbrev": "1.0.9", + "async": "1.5.2", + "istanbul-api": "1.3.7", + "js-yaml": "3.13.1", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "which": "1.3.1", + "wordwrap": "1.0.0" } }, "istanbul-api": { @@ -5008,17 +4448,17 @@ "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", "dev": true, "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" + "async": "2.6.3", + "fileset": "2.0.3", + "istanbul-lib-coverage": "1.2.1", + "istanbul-lib-hook": "1.2.2", + "istanbul-lib-instrument": "1.10.2", + "istanbul-lib-report": "1.1.5", + "istanbul-lib-source-maps": "1.2.6", + "istanbul-reports": "1.5.1", + "js-yaml": "3.13.1", + "mkdirp": "0.5.1", + "once": "1.4.0" }, "dependencies": { "async": { @@ -5027,7 +4467,7 @@ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { - "lodash": "^4.17.14" + "lodash": "4.17.15" } } } @@ -5044,7 +4484,7 @@ "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", "dev": true, "requires": { - "append-transform": "^0.4.0" + "append-transform": "0.4.0" } }, "istanbul-lib-instrument": { @@ -5053,13 +4493,13 @@ "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", "dev": true, "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" + "babel-generator": "6.26.1", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.2.1", + "semver": "5.7.1" } }, "istanbul-lib-report": { @@ -5068,10 +4508,10 @@ "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", "dev": true, "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" + "istanbul-lib-coverage": "1.2.1", + "mkdirp": "0.5.1", + "path-parse": "1.0.6", + "supports-color": "3.2.3" }, "dependencies": { "supports-color": { @@ -5080,7 +4520,7 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "1.0.0" } } } @@ -5091,11 +4531,11 @@ "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", "dev": true, "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" + "debug": "3.2.6", + "istanbul-lib-coverage": "1.2.1", + "mkdirp": "0.5.1", + "rimraf": "2.7.1", + "source-map": "0.5.7" }, "dependencies": { "debug": { @@ -5104,7 +4544,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "glob": { @@ -5113,12 +4553,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "ms": { @@ -5133,7 +4573,7 @@ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.6" } }, "source-map": { @@ -5150,7 +4590,7 @@ "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", "dev": true, "requires": { - "handlebars": "^4.0.3" + "handlebars": "4.7.2" } }, "jmespath": { @@ -5163,10 +4603,10 @@ "resolved": "https://registry.npmjs.org/joi/-/joi-8.4.2.tgz", "integrity": "sha1-vXd0ZY/pkFjYmU7R1LmWJITruFk=", "requires": { - "hoek": "4.x.x", - "isemail": "2.x.x", - "moment": "2.x.x", - "topo": "2.x.x" + "hoek": "4.2.1", + "isemail": "2.2.1", + "moment": "2.24.0", + "topo": "2.0.2" } }, "join-component": { @@ -5180,11 +4620,11 @@ "integrity": "sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ==", "dev": true, "requires": { - "config-chain": "^1.1.12", - "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "mkdirp": "~0.5.1", - "nopt": "~4.0.1" + "config-chain": "1.1.12", + "editorconfig": "0.15.3", + "glob": "7.1.6", + "mkdirp": "0.5.1", + "nopt": "4.0.1" }, "dependencies": { "glob": { @@ -5193,12 +4633,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "nopt": { @@ -5207,8 +4647,8 @@ "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.0.9", + "osenv": "0.1.5" } } } @@ -5230,8 +4670,8 @@ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "1.0.10", + "esprima": "4.0.1" }, "dependencies": { "esprima": { @@ -5269,7 +4709,7 @@ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "0.0.0" } }, "json-stringify-safe": { @@ -5282,7 +4722,91 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "requires": { - "minimist": "^1.2.0" + "minimist": "1.2.0" + } + }, + "jsondiffpatch": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz", + "integrity": "sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==", + "requires": { + "chalk": "2.4.2", + "diff-match-patch": "1.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "1.9.3" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jsondiffpatch": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz", + "integrity": "sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==", + "requires": { + "chalk": "^2.3.0", + "diff-match-patch": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } } }, "jsonfile": { @@ -5291,7 +4815,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.2.3" } }, "jsonify": { @@ -5328,16 +4852,16 @@ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" + "jws": "3.2.2", + "lodash.includes": "4.3.0", + "lodash.isboolean": "3.0.3", + "lodash.isinteger": "4.0.4", + "lodash.isnumber": "3.0.3", + "lodash.isplainobject": "4.0.6", + "lodash.isstring": "4.0.1", + "lodash.once": "4.1.1", + "ms": "2.1.2", + "semver": "5.7.1" }, "dependencies": { "ms": { @@ -5365,7 +4889,7 @@ "requires": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "jwks-rsa": { @@ -5374,12 +4898,12 @@ "integrity": "sha512-c/mFFq/wVXSkHzHGH+hLUwLeRKSCofNHJZKPzHho4YmO9LGwAazk7akfABvWhduS9OejWvqBS2jA69YeruEvNA==", "requires": { "@types/express-jwt": "0.0.42", - "debug": "^4.1.0", - "jsonwebtoken": "^8.5.1", - "limiter": "^1.1.4", - "lru-memoizer": "^2.0.1", - "ms": "^2.1.2", - "request": "^2.88.0" + "debug": "4.1.1", + "jsonwebtoken": "8.5.1", + "limiter": "1.1.5", + "lru-memoizer": "2.0.1", + "ms": "2.1.2", + "request": "2.88.0" }, "dependencies": { "debug": { @@ -5387,7 +4911,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -5402,8 +4926,8 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" + "jwa": "1.4.1", + "safe-buffer": "5.1.2" } }, "kind-of": { @@ -5412,7 +4936,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } }, "latest-version": { @@ -5421,7 +4945,7 @@ "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "dev": true, "requires": { - "package-json": "^4.0.0" + "package-json": "4.0.1" } }, "lazy-ass": { @@ -5460,8 +4984,8 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "1.1.2", + "type-check": "0.3.2" } }, "libpq": { @@ -5470,7 +4994,7 @@ "integrity": "sha512-herU0STiW3+/XBoYRycKKf49O9hBKK0JbdC2QmvdC5pyCSu8prb9idpn5bUSbxj8XwcEsWPWWWwTDZE9ZTwJ7g==", "requires": { "bindings": "1.5.0", - "nan": "^2.14.0" + "nan": "2.14.0" } }, "limiter": { @@ -5484,10 +5008,10 @@ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" + "graceful-fs": "4.2.3", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" } }, "locate-path": { @@ -5496,8 +5020,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "2.0.0", + "path-exists": "3.0.0" } }, "lodash": { @@ -5551,7 +5075,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "2.4.2" }, "dependencies": { "ansi-styles": { @@ -5560,7 +5084,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "chalk": { @@ -5569,9 +5093,9 @@ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "has-flag": { @@ -5586,7 +5110,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -5608,7 +5132,7 @@ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "js-tokens": "3.0.2" } }, "lowercase-keys": { @@ -5622,8 +5146,8 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", "requires": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, "lru-memoizer": { @@ -5631,8 +5155,8 @@ "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.0.1.tgz", "integrity": "sha512-kGl+zlIqdQL24f0Q9IUSUZeSvA7nqXPFLA7suFh00v4KVqfXkZJtkPfTfXV/oQMSPfNr6VT4xGkRAUPhFnGyxQ==", "requires": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" + "lodash.clonedeep": "4.5.0", + "lru-cache": "4.0.2" } }, "lru-queue": { @@ -5641,7 +5165,7 @@ "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", "dev": true, "requires": { - "es5-ext": "~0.10.2" + "es5-ext": "0.10.53" } }, "make-dir": { @@ -5650,7 +5174,7 @@ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { - "pify": "^3.0.0" + "pify": "3.0.0" }, "dependencies": { "pify": { @@ -5673,7 +5197,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "^1.0.0" + "object-visit": "1.0.1" } }, "math-random": { @@ -5694,14 +5218,14 @@ "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" + "d": "1.0.1", + "es5-ext": "0.10.53", + "es6-weak-map": "2.0.3", + "event-emitter": "0.3.5", + "is-promise": "2.1.0", + "lru-queue": "0.1.0", + "next-tick": "1.0.0", + "timers-ext": "0.1.7" } }, "memwatch-next": { @@ -5709,8 +5233,8 @@ "resolved": "https://registry.npmjs.org/memwatch-next/-/memwatch-next-0.3.0.tgz", "integrity": "sha1-IREFD5qQbgqi1ypOwPAInHhyb48=", "requires": { - "bindings": "^1.2.1", - "nan": "^2.3.2" + "bindings": "1.5.0", + "nan": "2.14.0" } }, "merge-descriptors": { @@ -5724,9 +5248,9 @@ "integrity": "sha1-49r41d7hDdLc59SuiNYrvud0drQ=", "requires": { "debug": "2.6.9", - "methods": "~1.1.2", - "parseurl": "~1.3.2", - "vary": "~1.1.2" + "methods": "1.1.2", + "parseurl": "1.3.3", + "vary": "1.1.2" } }, "methods": { @@ -5741,19 +5265,19 @@ "dev": true, "optional": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" } }, "millisecond": { @@ -5784,7 +5308,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -5798,8 +5322,8 @@ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "for-in": "1.0.2", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -5808,7 +5332,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -5865,7 +5389,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.1" } }, "find-up": { @@ -5874,7 +5398,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "glob": { @@ -5883,12 +5407,12 @@ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "has-flag": { @@ -5903,8 +5427,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "3.0.0", + "path-exists": "3.0.0" } }, "ms": { @@ -5919,7 +5443,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -5928,7 +5452,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -5943,7 +5467,7 @@ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -5958,7 +5482,7 @@ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz", "integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==", "requires": { - "moment": ">= 2.9.0" + "moment": "2.24.0" } }, "ms": { @@ -5983,9 +5507,9 @@ "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", "optional": true, "requires": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" + "mkdirp": "0.5.1", + "ncp": "2.0.0", + "rimraf": "2.4.5" } }, "nan": { @@ -5999,17 +5523,17 @@ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-windows": "1.0.2", + "kind-of": "6.0.3", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "arr-diff": { @@ -6052,8 +5576,7 @@ "neo-async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" }, "next-tick": { "version": "1.0.0", @@ -6066,7 +5589,7 @@ "resolved": "https://registry.npmjs.org/nice-simple-logger/-/nice-simple-logger-1.0.1.tgz", "integrity": "sha1-D55khSe+e+PkmrdvqMjAmK+VG/Y=", "requires": { - "lodash": "^4.3.0" + "lodash": "4.17.15" } }, "no-kafka": { @@ -6075,15 +5598,15 @@ "integrity": "sha512-hYnkg1OWVdaxORdzVvdQ4ueWYpf7IICObPzd24BBiDyVG5219VkUnRxSH9wZmisFb6NpgABzlSIL1pIZaCKmXg==", "requires": { "@types/bluebird": "3.5.0", - "@types/lodash": "^4.14.55", - "bin-protocol": "^3.1.1", - "bluebird": "^3.3.3", - "buffer-crc32": "^0.2.5", - "hashring": "^3.2.0", - "lodash": "=4.17.11", - "murmur-hash-js": "^1.0.0", - "nice-simple-logger": "^1.0.1", - "wrr-pool": "^1.0.3" + "@types/lodash": "4.14.149", + "bin-protocol": "3.1.1", + "bluebird": "3.7.2", + "buffer-crc32": "0.2.13", + "hashring": "3.2.0", + "lodash": "4.17.11", + "murmur-hash-js": "1.0.0", + "nice-simple-logger": "1.0.1", + "wrr-pool": "1.1.4" }, "dependencies": { "lodash": { @@ -6099,8 +5622,8 @@ "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", "dev": true, "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" + "object.getownpropertydescriptors": "2.1.0", + "semver": "5.7.1" } }, "nodemon": { @@ -6109,16 +5632,16 @@ "integrity": "sha512-VGPaqQBNk193lrJFotBU8nvWZPqEZY2eIzymy2jjY0fJ9qIsxA0sxQ8ATPl0gZC645gijYEc1jtZvpS8QWzJGQ==", "dev": true, "requires": { - "chokidar": "^2.1.8", - "debug": "^3.2.6", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.2", - "update-notifier": "^2.5.0" + "chokidar": "2.1.8", + "debug": "3.2.6", + "ignore-by-default": "1.0.1", + "minimatch": "3.0.4", + "pstree.remy": "1.1.7", + "semver": "5.7.1", + "supports-color": "5.5.0", + "touch": "3.1.0", + "undefsafe": "2.0.2", + "update-notifier": "2.5.0" }, "dependencies": { "anymatch": { @@ -6127,8 +5650,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "micromatch": "3.1.10", + "normalize-path": "2.1.1" }, "dependencies": { "normalize-path": { @@ -6137,7 +5660,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } } } @@ -6160,16 +5683,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -6178,7 +5701,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -6189,18 +5712,17 @@ "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "anymatch": "2.0.0", + "async-each": "1.0.3", + "braces": "2.3.2", + "glob-parent": "3.1.0", + "inherits": "2.0.4", + "is-binary-path": "1.0.1", + "is-glob": "4.0.1", + "normalize-path": "3.0.0", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1", + "upath": "1.2.0" } }, "debug": { @@ -6209,7 +5731,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" }, "dependencies": { "ms": { @@ -6226,13 +5748,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "debug": { @@ -6250,7 +5772,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -6259,7 +5781,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -6268,7 +5790,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6277,7 +5799,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -6288,7 +5810,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6297,7 +5819,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -6308,9 +5830,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -6327,14 +5849,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -6343,7 +5865,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -6352,7 +5874,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -6363,10 +5885,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -6375,7 +5897,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -6386,8 +5908,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" }, "dependencies": { "is-glob": { @@ -6396,7 +5918,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -6413,7 +5935,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -6422,7 +5944,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -6431,9 +5953,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.3" } }, "is-extglob": { @@ -6448,7 +5970,7 @@ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-number": { @@ -6457,7 +5979,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6466,7 +5988,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -6489,19 +6011,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "normalize-path": { @@ -6516,7 +6038,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -6527,7 +6049,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1" + "abbrev": "1.0.9" } }, "normalize-package-data": { @@ -6536,10 +6058,10 @@ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "hosted-git-info": "2.8.5", + "resolve": "1.15.0", + "semver": "5.7.1", + "validate-npm-package-license": "3.0.4" } }, "normalize-path": { @@ -6547,9 +6069,8 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, - "optional": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } }, "npm-run-path": { @@ -6558,7 +6079,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "^2.0.0" + "path-key": "2.0.1" } }, "number-is-nan": { @@ -6583,9 +6104,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" }, "dependencies": { "define-property": { @@ -6594,7 +6115,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -6617,7 +6138,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "^3.0.0" + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -6634,10 +6155,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "define-properties": "1.1.3", + "function-bind": "1.1.1", + "has-symbols": "1.0.1", + "object-keys": "1.1.1" } }, "object.entries": { @@ -6646,10 +6167,10 @@ "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "define-properties": "1.1.3", + "es-abstract": "1.17.4", + "function-bind": "1.1.1", + "has": "1.0.3" } }, "object.getownpropertydescriptors": { @@ -6658,8 +6179,8 @@ "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "define-properties": "1.1.3", + "es-abstract": "1.17.4" } }, "object.omit": { @@ -6669,8 +6190,8 @@ "dev": true, "optional": true, "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" + "for-own": "0.1.5", + "is-extendable": "0.1.1" } }, "object.pick": { @@ -6679,7 +6200,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -6696,10 +6217,10 @@ "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "define-properties": "1.1.3", + "es-abstract": "1.17.4", + "function-bind": "1.1.1", + "has": "1.0.3" } }, "on-finished": { @@ -6720,7 +6241,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "onetime": { @@ -6733,23 +6254,20 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "minimist": "0.0.10", + "wordwrap": "0.0.3" }, "dependencies": { "minimist": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" } } }, @@ -6758,12 +6276,12 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "word-wrap": "1.2.3" } }, "os-homedir": { @@ -6784,8 +6302,8 @@ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "output-file-sync": { @@ -6794,9 +6312,9 @@ "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", "dev": true, "requires": { - "graceful-fs": "^4.1.4", - "mkdirp": "^0.5.1", - "object-assign": "^4.1.0" + "graceful-fs": "4.2.3", + "mkdirp": "0.5.1", + "object-assign": "4.1.1" } }, "p-finally": { @@ -6811,7 +6329,7 @@ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "1.0.0" } }, "p-locate": { @@ -6820,7 +6338,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "1.3.0" } }, "p-try": { @@ -6835,10 +6353,10 @@ "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "dev": true, "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" + "got": "6.7.1", + "registry-auth-token": "3.4.0", + "registry-url": "3.1.0", + "semver": "5.7.1" } }, "packet-reader": { @@ -6853,10 +6371,10 @@ "dev": true, "optional": true, "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" } }, "parse-json": { @@ -6865,7 +6383,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "^1.2.0" + "error-ex": "1.3.2" } }, "parseurl": { @@ -6925,7 +6443,7 @@ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "pify": "^2.0.0" + "pify": "2.3.0" } }, "performance-now": { @@ -6941,10 +6459,10 @@ "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "0.1.3", - "pg-packet-stream": "^1.1.0", - "pg-pool": "^2.0.10", - "pg-types": "^2.1.0", - "pgpass": "1.x", + "pg-packet-stream": "1.1.0", + "pg-pool": "2.0.10", + "pg-types": "2.2.0", + "pgpass": "1.0.2", "semver": "4.3.2" }, "dependencies": { @@ -6970,8 +6488,8 @@ "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-3.0.0.tgz", "integrity": "sha512-qZZyywXJ8O4lbiIN7mn6vXIow1fd3QZFqzRe+uET/SZIXvCa3HBooXQA4ZU8EQX8Ae6SmaYtDGLp5DwU+8vrfg==", "requires": { - "libpq": "^1.7.0", - "pg-types": "^1.12.1", + "libpq": "1.8.9", + "pg-types": "1.13.0", "readable-stream": "1.0.31" }, "dependencies": { @@ -6981,10 +6499,10 @@ "integrity": "sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ==", "requires": { "pg-int8": "1.0.1", - "postgres-array": "~1.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.0", - "postgres-interval": "^1.1.0" + "postgres-array": "1.0.3", + "postgres-bytea": "1.0.0", + "postgres-date": "1.0.4", + "postgres-interval": "1.2.0" } }, "postgres-array": { @@ -6997,10 +6515,10 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.31.tgz", "integrity": "sha1-jyUC4LyeOw2huUUgqrtOJgPsr64=", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.4", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } } } @@ -7021,10 +6539,10 @@ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "requires": { "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" + "postgres-array": "2.0.0", + "postgres-bytea": "1.0.0", + "postgres-date": "1.0.4", + "postgres-interval": "1.2.0" } }, "pgpass": { @@ -7032,7 +6550,7 @@ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", "requires": { - "split": "^1.0.0" + "split": "1.0.1" } }, "pify": { @@ -7047,7 +6565,7 @@ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "^2.1.0" + "find-up": "2.1.0" } }, "pluralize": { @@ -7082,7 +6600,7 @@ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "requires": { - "xtend": "^4.0.0" + "xtend": "4.0.2" } }, "precond": { @@ -7141,7 +6659,7 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.1.2", "ipaddr.js": "1.9.0" } }, @@ -7188,9 +6706,9 @@ "dev": true, "optional": true, "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" + "is-number": "4.0.0", + "kind-of": "6.0.3", + "math-random": "1.0.4" }, "dependencies": { "is-number": { @@ -7231,10 +6749,10 @@ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" } }, "read-pkg": { @@ -7243,9 +6761,9 @@ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "load-json-file": "2.0.0", + "normalize-package-data": "2.5.0", + "path-type": "2.0.0" } }, "read-pkg-up": { @@ -7254,8 +6772,8 @@ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "find-up": "2.1.0", + "read-pkg": "2.0.0" } }, "readable-stream": { @@ -7263,10 +6781,10 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.4", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "readdirp": { @@ -7275,9 +6793,9 @@ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" + "graceful-fs": "4.2.3", + "micromatch": "3.1.10", + "readable-stream": "2.3.7" }, "dependencies": { "arr-diff": { @@ -7298,16 +6816,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -7316,7 +6834,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -7327,13 +6845,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -7342,7 +6860,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -7351,7 +6869,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -7360,7 +6878,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -7369,7 +6887,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -7380,7 +6898,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -7389,7 +6907,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -7400,9 +6918,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -7419,14 +6937,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -7435,7 +6953,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -7444,7 +6962,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -7455,10 +6973,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -7467,7 +6985,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -7478,7 +6996,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -7487,7 +7005,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -7496,9 +7014,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.3" } }, "is-number": { @@ -7507,7 +7025,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -7516,7 +7034,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -7545,19 +7063,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "readable-stream": { @@ -7566,13 +7084,13 @@ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -7581,7 +7099,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -7592,8 +7110,8 @@ "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", "mute-stream": "0.0.5" } }, @@ -7613,7 +7131,7 @@ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "resolve": "^1.1.6" + "resolve": "1.15.0" } }, "reconnect-core": { @@ -7621,7 +7139,7 @@ "resolved": "https://registry.npmjs.org/reconnect-core/-/reconnect-core-1.3.0.tgz", "integrity": "sha1-+65SkZp4d9hE4yRtAaLyZwHIM8g=", "requires": { - "backoff": "~2.5.0" + "backoff": "2.5.0" } }, "regenerate": { @@ -7642,9 +7160,9 @@ "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", "dev": true, "requires": { - "babel-runtime": "^6.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "private": "0.1.8" }, "dependencies": { "babel-runtime": { @@ -7653,8 +7171,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -7666,7 +7184,7 @@ "dev": true, "optional": true, "requires": { - "is-equal-shallow": "^0.1.3" + "is-equal-shallow": "0.1.3" } }, "regex-not": { @@ -7675,8 +7193,8 @@ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" } }, "regexpu-core": { @@ -7685,9 +7203,9 @@ "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", "dev": true, "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" + "regenerate": "1.4.0", + "regjsgen": "0.2.0", + "regjsparser": "0.1.5" } }, "registry-auth-token": { @@ -7696,8 +7214,8 @@ "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "dev": true, "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" + "rc": "1.2.8", + "safe-buffer": "5.1.2" } }, "registry-url": { @@ -7706,7 +7224,7 @@ "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "dev": true, "requires": { - "rc": "^1.0.1" + "rc": "1.2.8" } }, "regjsgen": { @@ -7721,7 +7239,7 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { - "jsesc": "~0.5.0" + "jsesc": "0.5.0" }, "dependencies": { "jsesc": { @@ -7761,7 +7279,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "^1.0.0" + "is-finite": "1.0.2" } }, "request": { @@ -7769,26 +7287,26 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "aws-sign2": "0.7.0", + "aws4": "1.9.1", + "caseless": "0.12.0", + "combined-stream": "1.0.8", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.3", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.26", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" }, "dependencies": { "form-data": { @@ -7796,9 +7314,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "asynckit": "0.4.0", + "combined-stream": "1.0.8", + "mime-types": "2.1.26" } }, "qs": { @@ -7826,8 +7344,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" + "caller-path": "0.1.0", + "resolve-from": "1.0.1" } }, "requires-port": { @@ -7841,7 +7359,7 @@ "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==", "dev": true, "requires": { - "path-parse": "^1.0.6" + "path-parse": "1.0.6" } }, "resolve-from": { @@ -7862,8 +7380,8 @@ "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", "dev": true, "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" + "exit-hook": "1.1.1", + "onetime": "1.1.0" } }, "ret": { @@ -7877,7 +7395,7 @@ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", "requires": { - "any-promise": "^1.3.0" + "any-promise": "1.3.0" } }, "rimraf": { @@ -7886,7 +7404,7 @@ "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", "optional": true, "requires": { - "glob": "^6.0.1" + "glob": "6.0.4" } }, "run-async": { @@ -7895,7 +7413,7 @@ "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", "dev": true, "requires": { - "once": "^1.3.0" + "once": "1.4.0" } }, "rx-lite": { @@ -7921,7 +7439,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "~0.1.10" + "ret": "0.1.15" } }, "safer-buffer": { @@ -7956,7 +7474,7 @@ "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "dev": true, "requires": { - "semver": "^5.0.3" + "semver": "5.7.1" } }, "send": { @@ -7965,18 +7483,18 @@ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "1.7.2", "mime": "1.6.0", "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "on-finished": "2.3.0", + "range-parser": "1.2.1", + "statuses": "1.5.0" }, "dependencies": { "ms": { @@ -7991,21 +7509,21 @@ "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.3.tgz", "integrity": "sha512-ptdeAxwTY0zbj7AK8m+SH3z52uHVrt/qmOTSIGo/kyfnSp3h5HeKlywkJf5GEk09kuRrPHfWARVSXH1W3IGU7g==", "requires": { - "bluebird": "^3.5.0", - "cls-bluebird": "^2.1.0", - "debug": "^4.1.1", - "dottie": "^2.0.0", + "bluebird": "3.7.2", + "cls-bluebird": "2.1.0", + "debug": "4.1.1", + "dottie": "2.0.2", "inflection": "1.12.0", - "lodash": "^4.17.15", - "moment": "^2.24.0", - "moment-timezone": "^0.5.21", - "retry-as-promised": "^3.2.0", - "semver": "^6.3.0", - "sequelize-pool": "^2.3.0", - "toposort-class": "^1.0.1", - "uuid": "^3.3.3", - "validator": "^10.11.0", - "wkx": "^0.4.8" + "lodash": "4.17.15", + "moment": "2.24.0", + "moment-timezone": "0.5.27", + "retry-as-promised": "3.2.0", + "semver": "6.3.0", + "sequelize-pool": "2.3.0", + "toposort-class": "1.0.1", + "uuid": "3.4.0", + "validator": "10.11.0", + "wkx": "0.4.8" }, "dependencies": { "debug": { @@ -8013,7 +7531,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -8039,14 +7557,14 @@ "integrity": "sha512-ZM4kUZvY3y14y+Rq3cYxGH7YDJz11jWHcN2p2x7rhAIemouu4CEXr5ebw30lzTBtyXV4j2kTO+nUjZOqzG7k+Q==", "dev": true, "requires": { - "bluebird": "^3.5.3", - "cli-color": "^1.4.0", - "fs-extra": "^7.0.1", - "js-beautify": "^1.8.8", - "lodash": "^4.17.5", - "resolve": "^1.5.0", - "umzug": "^2.1.0", - "yargs": "^13.1.0" + "bluebird": "3.7.2", + "cli-color": "1.4.0", + "fs-extra": "7.0.1", + "js-beautify": "1.10.3", + "lodash": "4.17.15", + "resolve": "1.15.0", + "umzug": "2.2.0", + "yargs": "13.3.0" } }, "sequelize-pool": { @@ -8059,9 +7577,9 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.3", "send": "0.17.1" } }, @@ -8077,10 +7595,10 @@ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" }, "dependencies": { "extend-shallow": { @@ -8089,7 +7607,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -8105,7 +7623,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "1.0.0" } }, "shebang-regex": { @@ -8120,9 +7638,9 @@ "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", "dev": true, "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" + "glob": "7.1.6", + "interpret": "1.2.0", + "rechoir": "0.6.2" }, "dependencies": { "glob": { @@ -8131,12 +7649,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -8172,7 +7690,7 @@ "formatio": "1.1.1", "lolex": "1.3.2", "samsam": "1.1.2", - "util": ">=0.10.3 <1" + "util": "0.12.1" } }, "sinon-chai": { @@ -8193,7 +7711,7 @@ "integrity": "sha512-SoltvxayTifWOgOGD6CTh+djcp5TaOa/zdbaA38wEH1ahF2azmiLOh8CPt6ExHf0pAJAsA9OCHTS7zK24Ym4yA==", "dev": true, "requires": { - "nan": ">=2.12.1" + "nan": "2.14.0" } }, "slice-ansi": { @@ -8208,14 +7726,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.3", + "use": "3.1.1" }, "dependencies": { "define-property": { @@ -8224,7 +7742,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -8233,7 +7751,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "source-map": { @@ -8250,9 +7768,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" }, "dependencies": { "define-property": { @@ -8261,7 +7779,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -8270,7 +7788,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -8279,7 +7797,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -8288,9 +7806,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.3" } }, "isobject": { @@ -8313,7 +7831,7 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "^3.2.0" + "kind-of": "3.2.2" } }, "source-map": { @@ -8327,11 +7845,11 @@ "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "dev": true, "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "atob": "2.1.2", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" } }, "source-map-support": { @@ -8340,7 +7858,7 @@ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "source-map": "^0.5.6" + "source-map": "0.5.7" }, "dependencies": { "source-map": { @@ -8363,8 +7881,8 @@ "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.5" } }, "spdx-exceptions": { @@ -8379,8 +7897,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "spdx-exceptions": "2.2.0", + "spdx-license-ids": "3.0.5" } }, "spdx-license-ids": { @@ -8394,7 +7912,7 @@ "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "requires": { - "through": "2" + "through": "2.3.8" } }, "split-string": { @@ -8403,7 +7921,7 @@ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "extend-shallow": "3.0.2" } }, "sprintf-js": { @@ -8416,15 +7934,15 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" }, "dependencies": { "jsbn": { @@ -8439,15 +7957,7 @@ "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", "requires": { - "escodegen": "^1.8.1" - } - }, - "static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", - "requires": { - "escodegen": "^1.8.1" + "escodegen": "1.13.0" } }, "static-extend": { @@ -8456,8 +7966,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "define-property": "0.2.5", + "object-copy": "0.1.0" }, "dependencies": { "define-property": { @@ -8466,7 +7976,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -8476,15 +7986,20 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string.prototype.trimleft": { @@ -8493,8 +8008,8 @@ "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "define-properties": "1.1.3", + "function-bind": "1.1.1" } }, "string.prototype.trimright": { @@ -8503,21 +8018,16 @@ "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "define-properties": "1.1.3", + "function-bind": "1.1.1" } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-bom": { @@ -8543,16 +8053,16 @@ "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" + "component-emitter": "1.3.0", + "cookiejar": "2.1.2", + "debug": "3.2.6", + "extend": "3.0.2", + "form-data": "2.5.1", + "formidable": "1.2.1", + "methods": "1.1.2", + "mime": "1.6.0", + "qs": "6.9.1", + "readable-stream": "2.3.7" }, "dependencies": { "debug": { @@ -8560,7 +8070,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "isarray": { @@ -8578,13 +8088,13 @@ "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -8592,7 +8102,7 @@ "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" + "safe-buffer": "5.1.2" } } } @@ -8608,8 +8118,8 @@ "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", "dev": true, "requires": { - "methods": "^1.1.2", - "superagent": "^3.8.3" + "methods": "1.1.2", + "superagent": "3.8.3" } }, "supports-color": { @@ -8627,7 +8137,7 @@ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz", "integrity": "sha512-f8SEn4YWkKh/HGK0ZjuA2VqA78i1aY6OIa5cqYNgOkBobfHV6Mz4dphQW/us8HYhEFfbENq329PyfIonWfzFrw==", "requires": { - "swagger-ui-dist": "^3.18.1" + "swagger-ui-dist": "3.25.0" } }, "table": { @@ -8636,12 +8146,12 @@ "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", "dev": true, "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.15", "slice-ansi": "0.0.4", - "string-width": "^2.0.0" + "string-width": "2.1.1" }, "dependencies": { "ajv": { @@ -8650,8 +8160,8 @@ "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", "dev": true, "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" + "co": "4.6.0", + "json-stable-stringify": "1.0.1" } }, "ansi-regex": { @@ -8672,8 +8182,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } }, "strip-ansi": { @@ -8682,24 +8192,23 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } }, "tc-core-library-js": { "version": "github:appirio-tech/tc-core-library-js#f45352974dafe5a10c86fc50bdd59ef399b50c65", - "from": "github:appirio-tech/tc-core-library-js#v2.6.3", "requires": { - "auth0-js": "^9.4.2", - "axios": "^0.19.0", - "bunyan": "^1.8.12", - "jsonwebtoken": "^8.3.0", - "jwks-rsa": "^1.3.0", - "le_node": "^1.3.1", - "lodash": "^4.17.10", - "millisecond": "^0.1.2", - "request": "^2.88.0" + "auth0-js": "9.12.2", + "axios": "0.19.2", + "bunyan": "1.8.12", + "jsonwebtoken": "8.5.1", + "jwks-rsa": "1.6.2", + "le_node": "1.8.0", + "lodash": "4.17.15", + "millisecond": "0.1.2", + "request": "2.88.0" } }, "term-size": { @@ -8708,7 +8217,7 @@ "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "dev": true, "requires": { - "execa": "^0.7.0" + "execa": "0.7.0" } }, "text-table": { @@ -8734,8 +8243,8 @@ "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", "dev": true, "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "es5-ext": "0.10.53", + "next-tick": "1.0.0" } }, "to-fast-properties": { @@ -8750,7 +8259,7 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "to-regex": { @@ -8759,10 +8268,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" } }, "to-regex-range": { @@ -8771,8 +8280,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "3.0.0", + "repeat-string": "1.6.1" }, "dependencies": { "is-number": { @@ -8781,7 +8290,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } } } @@ -8796,7 +8305,7 @@ "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", "requires": { - "hoek": "4.x.x" + "hoek": "4.2.1" } }, "toposort-class": { @@ -8810,7 +8319,7 @@ "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", "dev": true, "requires": { - "nopt": "~1.0.10" + "nopt": "1.0.10" }, "dependencies": { "nopt": { @@ -8819,7 +8328,7 @@ "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, "requires": { - "abbrev": "1" + "abbrev": "1.0.9" } } } @@ -8829,8 +8338,8 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "1.7.0", + "punycode": "1.4.1" }, "dependencies": { "punycode": { @@ -8856,7 +8365,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -8875,7 +8384,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "1.1.2" } }, "type-detect": { @@ -8890,7 +8399,7 @@ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "mime-types": "2.1.26" } }, "typedarray": { @@ -8903,11 +8412,10 @@ "version": "3.7.7", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.7.tgz", "integrity": "sha512-FeSU+hi7ULYy6mn8PKio/tXsdSXN35lm4KgV2asx00kzrLU9Pi3oAslcJT70Jdj7PHX29gGUPOT6+lXGBbemhA==", - "dev": true, "optional": true, "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" + "commander": "2.20.3", + "source-map": "0.6.1" } }, "umzug": { @@ -8916,8 +8424,8 @@ "integrity": "sha512-xZLW76ax70pND9bx3wqwb8zqkFGzZIK8dIHD9WdNy/CrNfjWcwQgQkGCuUqcuwEBvUm+g07z+qWvY+pxDmMEEw==", "dev": true, "requires": { - "babel-runtime": "^6.23.0", - "bluebird": "^3.5.3" + "babel-runtime": "6.26.0", + "bluebird": "3.7.2" }, "dependencies": { "babel-runtime": { @@ -8926,8 +8434,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } } } @@ -8938,7 +8446,7 @@ "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", "dev": true, "requires": { - "debug": "^2.2.0" + "debug": "2.6.9" } }, "underscore": { @@ -8957,10 +8465,10 @@ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "2.0.1" } }, "unique-string": { @@ -8969,7 +8477,7 @@ "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "dev": true, "requires": { - "crypto-random-string": "^1.0.0" + "crypto-random-string": "1.0.0" } }, "universalify": { @@ -8989,8 +8497,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "has-value": "0.3.1", + "isobject": "3.0.1" }, "dependencies": { "has-value": { @@ -8999,9 +8507,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" }, "dependencies": { "isobject": { @@ -9053,16 +8561,16 @@ "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", "dev": true, "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" + "boxen": "1.3.0", + "chalk": "2.4.2", + "configstore": "3.1.2", + "import-lazy": "2.1.0", + "is-ci": "1.2.1", + "is-installed-globally": "0.1.0", + "is-npm": "1.0.0", + "latest-version": "3.1.0", + "semver-diff": "2.1.0", + "xdg-basedir": "3.0.0" }, "dependencies": { "ansi-styles": { @@ -9071,7 +8579,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "chalk": { @@ -9080,9 +8588,9 @@ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "has-flag": { @@ -9097,7 +8605,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -9107,7 +8615,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "requires": { - "punycode": "^2.1.0" + "punycode": "2.1.1" }, "dependencies": { "punycode": { @@ -9142,8 +8650,8 @@ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" + "querystringify": "2.1.1", + "requires-port": "1.0.0" } }, "url-parse-lax": { @@ -9152,7 +8660,7 @@ "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, "requires": { - "prepend-http": "^1.0.1" + "prepend-http": "1.0.4" } }, "urlencode": { @@ -9160,7 +8668,7 @@ "resolved": "https://registry.npmjs.org/urlencode/-/urlencode-1.1.0.tgz", "integrity": "sha1-HyuibwE8hfATP3o61v8nMK33y7c=", "requires": { - "iconv-lite": "~0.4.11" + "iconv-lite": "0.4.24" } }, "use": { @@ -9181,11 +8689,11 @@ "integrity": "sha512-MREAtYOp+GTt9/+kwf00IYoHZyjM8VU4aVrkzUlejyqaIjd2GztVl5V9hGXKlvBKE3gENn/FMfHE5v6hElXGcQ==", "dev": true, "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "object.entries": "^1.1.0", - "safe-buffer": "^5.1.2" + "inherits": "2.0.4", + "is-arguments": "1.0.4", + "is-generator-function": "1.0.7", + "object.entries": "1.1.1", + "safe-buffer": "5.1.2" } }, "util-deprecate": { @@ -9209,7 +8717,7 @@ "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { - "user-home": "^1.1.1" + "user-home": "1.1.1" } }, "validate-npm-package-license": { @@ -9218,8 +8726,8 @@ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "spdx-correct": "3.1.0", + "spdx-expression-parse": "3.0.0" } }, "validator": { @@ -9237,9 +8745,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "which": { @@ -9248,7 +8756,7 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "which-module": { @@ -9263,7 +8771,7 @@ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "requires": { - "string-width": "^1.0.2 || 2" + "string-width": "1.0.2" } }, "widest-line": { @@ -9272,7 +8780,7 @@ "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { - "string-width": "^2.1.1" + "string-width": "2.1.1" }, "dependencies": { "ansi-regex": { @@ -9293,8 +8801,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } }, "strip-ansi": { @@ -9303,7 +8811,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -9318,7 +8826,7 @@ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==", "requires": { - "@types/node": "*" + "@types/node": "13.7.0" } }, "word-wrap": { @@ -9338,9 +8846,9 @@ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "3.2.1", + "string-width": "3.1.0", + "strip-ansi": "5.2.0" }, "dependencies": { "ansi-regex": { @@ -9355,7 +8863,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "is-fullwidth-code-point": { @@ -9370,9 +8878,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" } }, "strip-ansi": { @@ -9381,7 +8889,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "4.1.0" } } } @@ -9397,7 +8905,7 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "mkdirp": "0.5.1" } }, "write-file-atomic": { @@ -9406,9 +8914,9 @@ "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "graceful-fs": "4.2.3", + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" } }, "wrr-pool": { @@ -9416,7 +8924,7 @@ "resolved": "https://registry.npmjs.org/wrr-pool/-/wrr-pool-1.1.4.tgz", "integrity": "sha512-+lEdj42HlYqmzhvkZrx6xEymj0wzPBxqr7U1Xh9IWikMzOge03JSQT9YzTGq54SkOh/noViq32UejADZVzrgAg==", "requires": { - "lodash": "^4.17.11" + "lodash": "4.17.15" } }, "xdg-basedir": { @@ -9430,8 +8938,8 @@ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" + "sax": "1.2.1", + "xmlbuilder": "9.0.7" } }, "xmlbuilder": { @@ -9460,8 +8968,8 @@ "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", "requires": { - "argparse": "^1.0.7", - "glob": "^7.0.5" + "argparse": "1.0.10", + "glob": "7.1.6" }, "dependencies": { "glob": { @@ -9469,12 +8977,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -9485,16 +8993,16 @@ "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" + "cliui": "5.0.0", + "find-up": "3.0.0", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "require-main-filename": "2.0.0", + "set-blocking": "2.0.0", + "string-width": "3.1.0", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "13.1.1" }, "dependencies": { "ansi-regex": { @@ -9509,7 +9017,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "is-fullwidth-code-point": { @@ -9524,8 +9032,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "3.0.0", + "path-exists": "3.0.0" } }, "p-limit": { @@ -9534,7 +9042,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -9543,7 +9051,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -9558,9 +9066,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" } }, "strip-ansi": { @@ -9569,7 +9077,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "4.1.0" } } } @@ -9580,8 +9088,8 @@ "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "5.3.1", + "decamelize": "1.2.0" } }, "yargs-unparser": { @@ -9590,9 +9098,9 @@ "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" + "flat": "4.1.0", + "lodash": "4.17.15", + "yargs": "13.3.0" } } } diff --git a/package.json b/package.json index e1ccee32..57d728c5 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "sync:es:metadata": "./node_modules/.bin/babel-node migrations/elasticsearch_sync.js --index-name metadata", "migrate:es": "./node_modules/.bin/babel-node migrations/seedElasticsearchIndex.js", "migrate:es:metadata": "./node_modules/.bin/babel-node migrations/helpers/indexMetadataDirectly.js", + "migrate:bookmarks": "./node_modules/.bin/babel-node migrations/bookmarks/migrateBookmarksToLinks.js", + "migrate:bookmarks:revert": "./node_modules/.bin/babel-node migrations/bookmarks/migrateLinksToBookmarks.js", "prestart": "npm run -s build", "start": "node dist", "start:dev": "NODE_ENV=development PORT=8001 nodemon -w src --exec \"babel-node src --presets es2015\" | ./node_modules/.bin/bunyan", diff --git a/src/constants.js b/src/constants.js index 7385fe04..57dc1b87 100644 --- a/src/constants.js +++ b/src/constants.js @@ -215,7 +215,7 @@ export const CONNECT_NOTIFICATION_EVENT = { PROJECT_FILE_UPLOADED: 'connect.notification.project.fileUploaded', PROJECT_SPECIFICATION_MODIFIED: 'connect.notification.project.updated.spec', PROJECT_PROGRESS_MODIFIED: 'connect.notification.project.updated.progress', - PROJECT_FILES_UPDATED: 'connect.notification.project.files.updated', + PROJECT_ATTACHMENT_UPDATED: 'connect.notification.project.attachment.updated', PROJECT_TEAM_UPDATED: 'connect.notification.project.team.updated', // When phase is added/updated/deleted from the project, @@ -355,3 +355,8 @@ export const RESOURCES = { MILESTONE_TEMPLATE: 'milestone.template', ATTACHMENT: 'attachment', }; + +export const ATTACHMENT_TYPES = { + FILE: 'file', + LINK: 'link', +}; diff --git a/src/events/busApi.js b/src/events/busApi.js index 3bc92cae..1a334220 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -11,6 +11,7 @@ import { ROUTES, MILESTONE_STATUS, INVITE_STATUS, + ATTACHMENT_TYPES, } from '../constants'; import { createEvent } from '../services/busApi'; import models from '../models'; @@ -336,19 +337,32 @@ module.exports = (app, logger) => { where: { id: projectId }, }) .then((project) => { - createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILE_UPLOADED, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - fileName: attachment.filePath.replace(/^.*[\\\/]/, ''), // eslint-disable-line - fileUrl: connectProjectAttachmentUrl(projectId, attachment.id), - allowedUsers: attachment.allowedUsers, - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); + if (attachment.type === ATTACHMENT_TYPES.FILE) { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILE_UPLOADED, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + fileName: attachment.path.replace(/^.*[\\\/]/, ''), // eslint-disable-line + fileUrl: connectProjectAttachmentUrl(projectId, attachment.id), + allowedUsers: attachment.allowedUsers, + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + + if (attachment.type === ATTACHMENT_TYPES.LINK) { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_LINK_CREATED, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } - createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_ATTACHMENT_UPDATED, { projectId: project.id, projectName: project.name, refCode: _.get(project, 'details.utm.code'), @@ -376,7 +390,7 @@ module.exports = (app, logger) => { where: { id: projectId }, }) .then((project) => { - createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_ATTACHMENT_UPDATED, { projectId: project.id, projectName: project.name, refCode: _.get(project, 'details.utm.code'), @@ -404,7 +418,7 @@ module.exports = (app, logger) => { where: { id: projectId }, }) .then((project) => { - createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_ATTACHMENT_UPDATED, { projectId: project.id, projectName: project.name, refCode: _.get(project, 'details.utm.code'), diff --git a/src/events/index.js b/src/events/index.js index c4c121b8..5304010a 100644 --- a/src/events/index.js +++ b/src/events/index.js @@ -1,6 +1,7 @@ import { EVENT, CONNECT_NOTIFICATION_EVENT } from '../constants'; import { projectCreatedHandler, + projectCreatedHandlerForPhases, projectUpdatedKafkaHandler } from './projects'; import { projectPhaseAddedHandler, projectPhaseRemovedHandler, projectPhaseUpdatedHandler } from './projectPhases'; @@ -39,7 +40,7 @@ const voidRabbitHandler = (logger, msg, channel) => { // we should completely remove the handlers for this events. export const rabbitHandlers = { 'project.initial': projectCreatedHandler, // is only used `seedElasticsearchIndex.js` and can be removed - [EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED]: projectCreatedHandlerForPhases, // we have to call it, because it triggers topics creating for phases [EVENT.ROUTING_KEY.PROJECT_UPDATED]: voidRabbitHandler, // DISABLED [EVENT.ROUTING_KEY.PROJECT_DELETED]: voidRabbitHandler, // DISABLED [EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED]: voidRabbitHandler, // DISABLED @@ -73,7 +74,7 @@ export const rabbitHandlers = { export const kafkaHandlers = { // Events defined by project-api [CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED]: projectUpdatedKafkaHandler, - [CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED]: projectUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.PROJECT_ATTACHMENT_UPDATED]: projectUpdatedKafkaHandler, [CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED]: projectUpdatedKafkaHandler, [CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED]: projectUpdatedKafkaHandler, diff --git a/src/events/projects/index.js b/src/events/projects/index.js index ce5a1cec..e12db136 100644 --- a/src/events/projects/index.js +++ b/src/events/projects/index.js @@ -78,6 +78,31 @@ const projectCreatedHandler = Promise.coroutine(function* (logger, msg, channel) } }); +/** + * Handler for project creation event + * + * we call this handle only for the sake of creating topics for the phases + * + * @param {Object} logger logger to log along with trace id + * @param {Object} msg event payload + * @param {Object} channel channel to ack, nack + * @returns {undefined} + */ +const projectCreatedHandlerForPhases = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names + const project = JSON.parse(msg.content.toString()); + try { + if (project.phases && project.phases.length > 0) { + logger.debug('Phases found for the project, trying to create topics for each phase.'); + const topicPromises = _.map(project.phases, phase => createPhaseTopic(logger, phase)); + yield Promise.all(topicPromises); + } + channel.ack(msg); + } catch (error) { + logger.error(`Error processing event (projectId: ${project.id})`, error); + channel.nack(msg, false, !msg.fields.redelivered); + } +}); + /** * Handler for project updated event * @param {Object} logger logger to log along with trace id @@ -193,6 +218,7 @@ async function projectUpdatedKafkaHandler(app, topic, payload) { module.exports = { projectCreatedHandler, + projectCreatedHandlerForPhases, projectUpdatedHandler, projectDeletedHandler, projectUpdatedKafkaHandler, diff --git a/src/models/projectAttachment.js b/src/models/projectAttachment.js index 6e23deb9..1dcd56c5 100644 --- a/src/models/projectAttachment.js +++ b/src/models/projectAttachment.js @@ -1,14 +1,24 @@ +import _ from 'lodash'; +import { ATTACHMENT_TYPES } from '../constants'; module.exports = function defineProjectAttachment(sequelize, DataTypes) { const ProjectAttachment = sequelize.define('ProjectAttachment', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, title: { type: DataTypes.STRING, allowNull: true }, + type: { + type: DataTypes.STRING, + allowNull: false, + validate: { + isIn: [_.values(ATTACHMENT_TYPES)], + }, + }, + tags: DataTypes.ARRAY({ type: DataTypes.STRING, allowNull: true }), size: { type: DataTypes.INTEGER, allowNull: true }, // size in MB category: { type: DataTypes.STRING, allowNull: true }, // size in MB description: { type: DataTypes.STRING, allowNull: true }, - filePath: { type: DataTypes.STRING, allowNull: false }, - contentType: { type: DataTypes.STRING, allowNull: false }, + path: { type: DataTypes.STRING, allowNull: false }, + contentType: { type: DataTypes.STRING, allowNull: true }, allowedUsers: DataTypes.ARRAY({ type: DataTypes.INTEGER, allowNull: true }), deletedAt: { type: DataTypes.DATE, allowNull: true }, createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, diff --git a/src/models/projectMemberInvite.js b/src/models/projectMemberInvite.js index bacee6cd..d19db4cf 100644 --- a/src/models/projectMemberInvite.js +++ b/src/models/projectMemberInvite.js @@ -63,6 +63,45 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) { raw: true, }); + ProjectMemberInvite.getPendingOrRequestedProjectInvitesForUser = (projectId, email, userId) => { + const where = { + projectId, + status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] }, + }; + + if (email && userId) { + _.assign(where, { $or: [{ email: { $eq: email } }, { userId: { $eq: userId } }] }); + } else if (email) { + _.assign(where, { email }); + } else if (userId) { + _.assign(where, { userId }); + } + return ProjectMemberInvite.findAll({ + where, + raw: true, + }); + }; + + ProjectMemberInvite.getPendingInviteByIdForUser = (projectId, inviteId, email, userId) => { + const where = { + projectId, + id: inviteId, + status: INVITE_STATUS.PENDING, + }; + + if (email && userId) { + _.assign(where, { $or: [{ email: { $eq: email } }, { userId: { $eq: userId } }] }); + } else if (email) { + _.assign(where, { email }); + } else if (userId) { + _.assign(where, { userId }); + } + return ProjectMemberInvite.findOne({ + where, + raw: true, + }); + }; + ProjectMemberInvite.getPendingInviteByEmailOrUserId = (projectId, email, userId) => { const where = { projectId, status: INVITE_STATUS.PENDING }; @@ -107,5 +146,13 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) { }).then(res => _.without(_.map(res, 'projectId'), null)); }; + ProjectMemberInvite.getPendingOrRequestedProjectInviteById = (projectId, inviteId) => ProjectMemberInvite.findOne({ + where: { + projectId, + id: inviteId, + status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] }, + }, + }); + return ProjectMemberInvite; }; diff --git a/src/permissions/constants.js b/src/permissions/constants.js index 570bc152..9033fc1e 100644 --- a/src/permissions/constants.js +++ b/src/permissions/constants.js @@ -13,9 +13,12 @@ * - `ROLES_PROJECT_MEMBERS` * - `ROLES_ADMINS` */ +import _ from 'lodash'; import { PROJECT_MEMBER_ROLE, + PROJECT_MEMBER_MANAGER_ROLES, ADMIN_ROLES, + USER_ROLE, } from '../constants'; export const PERMISSION = { // eslint-disable-line import/prefer-default-export @@ -32,8 +35,37 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export PROJECT_MEMBER_ROLE.COPILOT, ], }, + /** * Permissions defined by logic: **WHAT** can be done with such a permission. */ + + /* + * Update invite permissions + */ + UPDATE_NOT_OWN_INVITE: { + topcoderRoles: [USER_ROLE.TOPCODER_ADMIN, USER_ROLE.CONNECT_ADMIN], + }, + + UPDATE_REQUESTED_INVITE: { + topcoderRoles: [USER_ROLE.TOPCODER_ADMIN, USER_ROLE.CONNECT_ADMIN, USER_ROLE.COPILOT_MANAGER], + }, + + /* + * Delete invite permissions + */ + DELETE_CUSTOMER_INVITE: { + topcoderRoles: [USER_ROLE.TOPCODER_ADMIN, USER_ROLE.CONNECT_ADMIN], + projectRoles: _.values(PROJECT_MEMBER_ROLE), // any project member + }, + + DELETE_NON_CUSTOMER_INVITE: { + topcoderRoles: [USER_ROLE.TOPCODER_ADMIN, USER_ROLE.CONNECT_ADMIN], + projectRoles: PROJECT_MEMBER_MANAGER_ROLES, + }, + + DELETE_REQUESTED_INVITE: { + topcoderRoles: [USER_ROLE.TOPCODER_ADMIN, USER_ROLE.CONNECT_ADMIN, USER_ROLE.COPILOT_MANAGER], + }, }; diff --git a/src/permissions/index.js b/src/permissions/index.js index 52149622..30c3dad3 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -12,6 +12,8 @@ const connectManagerOrAdmin = require('./connectManagerOrAdmin.ops'); const copilotAndAbove = require('./copilotAndAbove'); const workManagementPermissions = require('./workManagementForTemplate'); const projectSettingEdit = require('./projectSetting.edit'); +const projectMemberInviteView = require('./projectMemberInvite.view'); +const projectAnyAuthUser = require('./project.anyAuthUser'); module.exports = () => { Authorizer.setDeniedStatusCode(403); @@ -85,9 +87,10 @@ module.exports = () => { Authorizer.setPolicy('metadata.list', true); // anyone can view all metadata Authorizer.setPolicy('projectMemberInvite.create', projectView); - Authorizer.setPolicy('projectMemberInvite.put', true); - Authorizer.setPolicy('projectMemberInvite.get', true); - Authorizer.setPolicy('projectMemberInvite.list', projectView); + Authorizer.setPolicy('projectMemberInvite.edit', projectAnyAuthUser); + Authorizer.setPolicy('projectMemberInvite.delete', projectAnyAuthUser); + Authorizer.setPolicy('projectMemberInvite.get', projectMemberInviteView); + Authorizer.setPolicy('projectMemberInvite.list', projectMemberInviteView); Authorizer.setPolicy('form.create', projectAdmin); Authorizer.setPolicy('form.edit', projectAdmin); diff --git a/src/permissions/project.anyAuthUser.js b/src/permissions/project.anyAuthUser.js new file mode 100644 index 00000000..f0a90ae2 --- /dev/null +++ b/src/permissions/project.anyAuthUser.js @@ -0,0 +1,27 @@ +/** + * Allow any logged-in users to access project based URL. + * + * The main purpose of using this policy is to populate `req.context.currentProjectMembers`. + * + * NOTE + * This policy can be only applied for routes with projectId. + */ + +import _ from 'lodash'; +import models from '../models'; + +module.exports = (req) => { + if (_.isUndefined(req.params.projectId)) { + return Promise.reject(new Error('Policy "project.anyAuthUser" cannot be used for route without "projectId".')); + } + + const projectId = _.parseInt(req.params.projectId); + + return models.ProjectMember.getActiveProjectMembers(projectId) + .then((members) => { + req.context = req.context || {}; + req.context.currentProjectMembers = members; + + return true; + }); +}; diff --git a/src/permissions/projectMember.delete.js b/src/permissions/projectMember.delete.js index 5f4bb946..eb0a7bc0 100644 --- a/src/permissions/projectMember.delete.js +++ b/src/permissions/projectMember.delete.js @@ -26,6 +26,7 @@ module.exports = freq => new Promise((resolve, reject) => { // check if auth user has acecss to this project const hasAccess = util.hasAdminRole(req) || (authMember && memberToBeRemoved && ([ + PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, PROJECT_MEMBER_ROLE.PROJECT_MANAGER, diff --git a/src/permissions/projectMemberInvite.view.js b/src/permissions/projectMemberInvite.view.js new file mode 100644 index 00000000..138555cf --- /dev/null +++ b/src/permissions/projectMemberInvite.view.js @@ -0,0 +1,36 @@ + +import _ from 'lodash'; +import util from '../util'; +import models from '../models'; +import { MANAGER_ROLES } from '../constants'; + +/** + * Check user can view project member invite or not. + * Users who can view the project can see all invites. Logged-in user can only see invitations + * for himself/herself. + * @param {Object} freq the express request instance + * @return {Promise} Returns a promise + */ +module.exports = freq => new Promise((resolve) => { + const req = freq; + const projectId = _.parseInt(freq.params.projectId); + const currentUserId = freq.authUser.userId; + let hasAccess; + return models.ProjectMember.getActiveProjectMembers(projectId) + .then((members) => { + req.context = req.context || {}; + // check if auth user has acecss to this project + hasAccess = util.hasAdminRole(req) + || util.hasRoles(req, MANAGER_ROLES) + || !_.isUndefined(_.find(members, m => m.userId === currentUserId)); + if (hasAccess) { + // if user can "view" the project, he/she can see all invites + // save this info into request. + req.context.inviteType = 'all'; + } else { + // user can only see invitations for himself/herself in this project + req.context.inviteType = 'list'; + } + return resolve(true); + }); +}); diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index 54fd0d7d..81525e1f 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -7,11 +7,11 @@ import validate from 'express-validation'; import _ from 'lodash'; import config from 'config'; import Joi from 'joi'; -import path from 'path'; +import Path from 'path'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { EVENT, RESOURCES } from '../../constants'; +import { EVENT, RESOURCES, ATTACHMENT_TYPES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -21,9 +21,11 @@ const addAttachmentValidations = { description: Joi.string().optional().allow(null).allow(''), category: Joi.string().optional().allow(null).allow(''), size: Joi.number().optional(), - filePath: Joi.string().required(), - s3Bucket: Joi.string().required(), - contentType: Joi.string().required(), + path: Joi.string().required(), + type: Joi.string().required().valid(_.values(ATTACHMENT_TYPES)), + tags: Joi.array().items(Joi.string().min(1)).optional(), + s3Bucket: Joi.string().when('type', { is: ATTACHMENT_TYPES.FILE, then: Joi.string().required() }), + contentType: Joi.string().when('type', { is: ATTACHMENT_TYPES.FILE, then: Joi.string().required() }), allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null), }).required(), }; @@ -48,9 +50,9 @@ module.exports = [ }); // extract file name - const fileName = path.parse(data.filePath).base; + const fileName = Path.parse(data.path).base; // create file path - const filePath = _.join([ + const path = _.join([ config.get('projectAttachmentPathPrefix'), data.projectId, config.get('projectAttachmentPathPrefix'), @@ -67,19 +69,13 @@ module.exports = [ } const sourceBucket = data.s3Bucket; - const sourceKey = data.filePath; + const sourceKey = data.path; const destBucket = config.get('attachmentsS3Bucket'); - const destKey = filePath; + const destKey = path; - // don't actually transfer file in development mode if file uploading is disabled, so we can test this endpoint - const fileTransferPromise = (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true') - ? util.s3FileTransfer(req, sourceBucket, sourceKey, destBucket, destKey) - : Promise.resolve(); - - fileTransferPromise.then(() => { - // file copied to final destination, create DB record - req.log.debug('creating db record'); - return models.ProjectAttachment.create({ + if (data.type === ATTACHMENT_TYPES.LINK) { + // We create the record in the db and return (i.e. no need to handle transferring file between S3 buckets) + Promise.resolve(models.ProjectAttachment.create({ projectId, allowedUsers, createdBy: req.authUser.userId, @@ -89,75 +85,127 @@ module.exports = [ category: data.category || null, description: data.description, contentType: data.contentType, - filePath, + path: data.path, + type: data.type, + tags: data.tags, + })).then((_link) => { + const link = _link.get({ plain: true }); + req.log.debug('New Link Attachment record: ', link); + + // publish Rabbit MQ event + req.app.services.pubsub.publish( + EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, + link, + { correlationId: req.id }, + ); + + // emit the Kafka event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, + RESOURCES.ATTACHMENT, + link); + + res.status(201).json(link); + return Promise.resolve(); + }) + .catch((error) => { + req.log.error('Error adding link attachment', error); + const rerr = error; + rerr.status = rerr.status || 500; + next(rerr); }); - }).then((_newAttachment) => { - newAttachment = _newAttachment.get({ plain: true }); - req.log.debug('New Attachment record: ', newAttachment); - if (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true') { - // retrieve download url for the response - req.log.debug('retrieving download url'); - return httpClient.post(`${fileServiceUrl}downloadurl`, { - param: { - filePath, - }, + } else { + // don't actually transfer file in development mode if file uploading is disabled, so we can test this endpoint + const fileTransferPromise = (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true') + ? util.s3FileTransfer(req, sourceBucket, sourceKey, destBucket, destKey) + : Promise.resolve(); + + fileTransferPromise.then(() => { + // file copied to final destination, create DB record + req.log.debug('creating db file record'); + return models.ProjectAttachment.create({ + projectId, + allowedUsers, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + title: data.title, + size: data.size, + category: data.category || null, + description: data.description, + contentType: data.contentType, + path, + type: data.type, + tags: data.tags, }); - } - return Promise.resolve(); - }).then((resp) => { - if (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true') { - req.log.debug('Retreiving Presigned Url resp: ', JSON.stringify(resp.data)); - return new Promise((accept, reject) => { - if (resp.status !== 200 || resp.data.result.status !== 200) { - reject(new Error('Unable to fetch pre-signed url')); - } else { - let response = _.cloneDeep(newAttachment); - response = _.omit(response, ['filePath', 'deletedAt']); + }).then((_newAttachment) => { + newAttachment = _newAttachment.get({ plain: true }); + req.log.debug('New Attachment record: ', newAttachment); + if (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true') { + // retrieve download url for the response + req.log.debug('retrieving download url'); + return httpClient.post(`${fileServiceUrl}downloadurl`, { + param: { + filePath: path, + }, + }); + } + return Promise.resolve(); + }).then((resp) => { + if (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true') { + req.log.debug('Retreiving Presigned Url resp: ', JSON.stringify(resp.data)); + return new Promise((accept, reject) => { + if (resp.status !== 200 || resp.data.result.status !== 200) { + reject(new Error('Unable to fetch pre-signed url')); + } else { + let response = _.cloneDeep(newAttachment); + response = _.omit(response, ['path', 'deletedAt']); - response.downloadUrl = resp.data.result.content.preSignedURL; - // publish event - req.app.services.pubsub.publish( + response.downloadUrl = resp.data.result.content.preSignedURL; + // publish event + req.app.services.pubsub.publish( EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, newAttachment, { correlationId: req.id }, ); - // emit the event - util.sendResourceToKafkaBus( + // emit the event + util.sendResourceToKafkaBus( req, EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, RESOURCES.ATTACHMENT, newAttachment); - res.status(201).json(response); - accept(); - } - }); - } - let response = _.cloneDeep(newAttachment); - response = _.omit(response, ['filePath', 'deletedAt']); - // only in development mode - response.downloadUrl = filePath; - // publish event - req.app.services.pubsub.publish( + res.status(201).json(response); + accept(); + } + }); + } + let response = _.cloneDeep(newAttachment); + response = _.omit(response, ['path', 'deletedAt']); + // only in development mode + response.downloadUrl = path; + // publish event + req.app.services.pubsub.publish( EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, newAttachment, { correlationId: req.id }, ); - // emit the event - util.sendResourceToKafkaBus( + // emit the event + util.sendResourceToKafkaBus( req, EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, RESOURCES.ATTACHMENT, newAttachment); - res.status(201).json(response); - return Promise.resolve(); - }) - .catch((error) => { - req.log.error('Error adding attachment', error); - const rerr = error; - rerr.status = rerr.status || 500; - next(rerr); - }); + res.status(201).json(response); + return Promise.resolve(); + }) + .catch((error) => { + req.log.error('Error adding file attachment', error); + const rerr = error; + rerr.status = rerr.status || 500; + next(rerr); + }); + } }, ]; diff --git a/src/routes/attachments/create.spec.js b/src/routes/attachments/create.spec.js index e7e02940..d3bcf819 100644 --- a/src/routes/attachments/create.spec.js +++ b/src/routes/attachments/create.spec.js @@ -1,25 +1,37 @@ /* eslint-disable no-unused-expressions */ import chai from 'chai'; import sinon from 'sinon'; +import _ from 'lodash'; import request from 'supertest'; import server from '../../app'; import models from '../../models'; import util from '../../util'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT, ATTACHMENT_TYPES } from '../../constants'; const should = chai.should(); -const body = { +const fileAttachmentBody = { title: 'Spec.pdf', - description: '', + description: 'attachment file description', category: 'appDefinition', - filePath: 'projects/1/spec.pdf', + path: 'projects/1/spec.pdf', + type: ATTACHMENT_TYPES.FILE, + tags: ['tag1', 'tag2', 'tag3'], s3Bucket: 'submissions-staging-dev', contentType: 'application/pdf', }; +const linkAttachmentBody = { + title: 'link title', + description: 'link description', + category: 'appDefinition', + path: 'https://connect.topcoder-dev.com/projects/8600/assets', + type: ATTACHMENT_TYPES.LINK, + tags: ['tag4', 'tag5'], +}; + describe('Project Attachments', () => { let project1; let postSpy; @@ -38,7 +50,7 @@ describe('Project Attachments', () => { success: true, status: 200, content: { - filePath: 'tmp/spec.pdf', + path: 'tmp/spec.pdf', preSignedURL: 'www.topcoder.com/media/spec.pdf', }, }, @@ -51,7 +63,7 @@ describe('Project Attachments', () => { success: true, status: 200, content: { - filePath: 'tmp/spec.pdf', + path: 'tmp/spec.pdf', preSignedURL: 'http://topcoder-media.s3.amazon.com/projects/1/spec.pdf', }, }, @@ -107,18 +119,42 @@ describe('Project Attachments', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send(body) + .send(fileAttachmentBody) .expect('Content-Type', /json/) .expect(403, done); }); - it('should return 201 return attachment record', (done) => { + it('should return 400 if contentType is not provided for file attachment', (done) => { + const payload = _.omit(_.cloneDeep(fileAttachmentBody), 'contentType'); + request(server) + .post(`/v5/projects/${project1.id}/attachments/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(payload) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 if s3Bucket is not provided for file attachment', (done) => { + const payload = _.omit(_.cloneDeep(fileAttachmentBody), 's3Bucket'); + request(server) + .post(`/v5/projects/${project1.id}/attachments/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(payload) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should properly create file attachment - 201', (done) => { request(server) .post(`/v5/projects/${project1.id}/attachments/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send(body) + .send(fileAttachmentBody) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { @@ -130,7 +166,9 @@ describe('Project Attachments', () => { postSpy.should.have.been.calledOnce; getSpy.should.have.been.calledOnce; stub.restore(); - resJson.title.should.equal('Spec.pdf'); + resJson.title.should.equal(fileAttachmentBody.title); + resJson.tags.should.eql(fileAttachmentBody.tags); + resJson.type.should.eql(fileAttachmentBody.type); resJson.downloadUrl.should.exist; resJson.projectId.should.equal(project1.id); done(); @@ -138,6 +176,35 @@ describe('Project Attachments', () => { }); }); + it('should properly create link attachment - 201', (done) => { + request(server) + .post(`/v5/projects/${project1.id}/attachments/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(linkAttachmentBody) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + postSpy.should.have.been.calledOnce; + getSpy.should.have.been.calledOnce; + stub.restore(); + resJson.title.should.equal(linkAttachmentBody.title); + resJson.path.should.equal(linkAttachmentBody.path); + resJson.description.should.equal(linkAttachmentBody.description); + resJson.type.should.equal(linkAttachmentBody.type); + resJson.tags.should.eql(linkAttachmentBody.tags); + resJson.projectId.should.equal(project1.id); + done(); + } + }); + }); + describe('Bus api', () => { let createEventSpy; @@ -150,13 +217,13 @@ describe('Project Attachments', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends send correct BUS API messages when attachment added', (done) => { + it('should send correct BUS API messages when file attachment added', (done) => { request(server) .post(`/v5/projects/${project1.id}/attachments/`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send(body) + .send(fileAttachmentBody) .expect(201) .end((err) => { if (err) { @@ -168,16 +235,61 @@ describe('Project Attachments', () => { createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, sinon.match({ resource: RESOURCES.ATTACHMENT, - title: body.title, - description: body.description, - category: body.category, - contentType: body.contentType, + title: fileAttachmentBody.title, + description: fileAttachmentBody.description, + category: fileAttachmentBody.category, + contentType: fileAttachmentBody.contentType, + type: fileAttachmentBody.type, + tags: fileAttachmentBody.tags, })).should.be.true; // Check Notification Service events createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILE_UPLOADED) .should.be.true; - createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, sinon.match({ + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_ATTACHMENT_UPDATED, sinon.match({ + projectId: project1.id, + projectName: project1.name, + projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, + userId: 40051333, + initiatorUserId: 40051333, + })).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when link attachment added', (done) => { + request(server) + .post(`/v5/projects/${project1.id}/attachments/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(linkAttachmentBody) + .expect(201) + .end((err) => { + if (err) { + done(err); + } else { + // Wait for app message handler to complete + testUtil.wait(() => { + createEventSpy.calledThrice.should.be.true; + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, sinon.match({ + resource: RESOURCES.ATTACHMENT, + title: linkAttachmentBody.title, + description: linkAttachmentBody.description, + category: linkAttachmentBody.category, + type: linkAttachmentBody.type, + path: linkAttachmentBody.path, + tags: linkAttachmentBody.tags, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_LINK_CREATED) + .should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_ATTACHMENT_UPDATED, sinon.match({ projectId: project1.id, projectName: project1.name, projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, diff --git a/src/routes/attachments/delete.js b/src/routes/attachments/delete.js index 99dca3a1..9d961b01 100644 --- a/src/routes/attachments/delete.js +++ b/src/routes/attachments/delete.js @@ -9,7 +9,7 @@ import config from 'config'; import models from '../../models'; import util from '../../util'; import fileService from '../../services/fileService'; -import { EVENT, RESOURCES } from '../../constants'; +import { EVENT, RESOURCES, ATTACHMENT_TYPES } from '../../constants'; /** * API to delete a project member. @@ -43,8 +43,9 @@ module.exports = [ .then(() => _attachment.destroy()); })) .then((_attachment) => { - if (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true') { - return fileService.deleteFile(req, _attachment.filePath); + if (_attachment.type === ATTACHMENT_TYPES.FILE && + (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true')) { + return fileService.deleteFile(req, _attachment.path); } return Promise.resolve(); }) diff --git a/src/routes/attachments/delete.spec.js b/src/routes/attachments/delete.spec.js index d31d1718..680e1ea9 100644 --- a/src/routes/attachments/delete.spec.js +++ b/src/routes/attachments/delete.spec.js @@ -9,15 +9,17 @@ import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT, ATTACHMENT_TYPES } from '../../constants'; const should = chai.should(); // eslint-disable-line no-unused-vars describe('Project Attachments delete', () => { let project1; - let attachment; + let attachments = []; beforeEach((done) => { + attachments = []; testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => { models.Project.create({ type: 'generic', @@ -41,19 +43,37 @@ describe('Project Attachments delete', () => { isPrimary: true, createdBy: 1, updatedBy: 1, - }).then(() => models.ProjectAttachment.create({ + }).then(() => + models.ProjectAttachment.create({ projectId: project1.id, - title: 'test.txt', + title: 'file1.txt', description: 'blah', contentType: 'application/unknown', size: 12312, category: null, - filePath: 'https://media.topcoder.com/projects/1/test.txt', + path: 'https://media.topcoder.com/projects/1/test.txt', + type: ATTACHMENT_TYPES.FILE, + tags: ['tag1', 'tag2'], createdBy: testUtil.userIds.copilot, updatedBy: 1, - }).then((a1) => { - attachment = a1; - done(); + }).then((file) => { + attachments.push(file); + models.ProjectAttachment.create( + { + projectId: project1.id, + title: 'Test Link 1', + description: 'Test link 1 description', + size: 123456, + category: null, + path: 'https://connect.topcoder-dev.com/projects/8600/assets', + type: ATTACHMENT_TYPES.LINK, + tags: ['tag3', 'tag4'], + createdBy: testUtil.userIds.copilot, + updatedBy: 1, + }).then((link) => { + attachments.push(link); + done(); + }); })); }); }); @@ -74,7 +94,7 @@ describe('Project Attachments delete', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) + .delete(`/v5/projects/${project1.id}/attachments/${attachments[0].id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -92,7 +112,8 @@ describe('Project Attachments delete', () => { .expect(404, done); }); - it('should return 204 if the CREATOR removes the attachment successfully', (done) => { + + it('should return 204 if the CREATOR removes the file attachment successfully', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { delete: () => Promise.resolve({ status: 200, @@ -110,7 +131,7 @@ describe('Project Attachments delete', () => { const deleteSpy = sinon.spy(mockHttpClient, 'delete'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) + .delete(`/v5/projects/${project1.id}/attachments/${attachments[0].id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -123,7 +144,7 @@ describe('Project Attachments delete', () => { models.ProjectAttachment.findOne({ where: { projectId: project1.id, - id: attachment.id, + id: attachments[0].id, }, paranoid: false, }) @@ -137,7 +158,84 @@ describe('Project Attachments delete', () => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`) + .get(`/v5/projects/${project1.id}/attachments/${attachments[0].id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + } + }), 500); + } + }); + }); + + it('should return 204 if ADMIN deletes the file attachment successfully', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/attachments/${attachments[0].id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ userId: 1, projectId: project1.id, role: 'customer' }) + .expect(204, done) + .end((err) => { + if (err) { + done(err); + } else { + request(server) + .get(`/v5/projects/${project1.id}/attachments/${attachments[0].id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + } + }); + }); + + it('should return 204 if the CREATOR removes the link attachment successfully', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + delete: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: true, + }, + }, + }), + }); + const deleteSpy = sinon.spy(mockHttpClient, 'delete'); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .delete(`/v5/projects/${project1.id}/attachments/${attachments[1].id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(204) + .end((err) => { + if (err) { + done(err); + } else { + setTimeout(() => + models.ProjectAttachment.findOne({ + where: { + projectId: project1.id, + id: attachments[1].id, + }, + paranoid: false, + }) + .then((res) => { + if (!res) { + throw new Error('Should found the entity'); + } else { + deleteSpy.called.should.be.false; + chai.assert.isNotNull(res.deletedAt); + chai.assert.isNotNull(res.deletedBy); + + request(server) + .get(`/v5/projects/${project1.id}/attachments/${attachments[1].id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -148,9 +246,9 @@ describe('Project Attachments delete', () => { }); }); - it('should return 204 if ADMIN deletes the attachment successfully', (done) => { + it('should return 204 if ADMIN deletes the link attachment successfully', (done) => { request(server) - .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) + .delete(`/v5/projects/${project1.id}/attachments/${attachments[1].id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -161,7 +259,7 @@ describe('Project Attachments delete', () => { done(err); } else { request(server) - .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`) + .get(`/v5/projects/${project1.id}/attachments/${attachments[1].id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -182,9 +280,44 @@ describe('Project Attachments delete', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends send correct BUS API messages when attachment deleted', (done) => { + it('sends send correct BUS API messages when file attachment deleted', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/attachments/${attachments[0].id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end((err) => { + if (err) { + done(err); + } else { + // Wait for app message handler to complete + testUtil.wait(() => { + createEventSpy.calledTwice.should.be.true; + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, sinon.match({ + resource: RESOURCES.ATTACHMENT, + id: attachments[0].id, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_ATTACHMENT_UPDATED, sinon.match({ + projectId: project1.id, + projectName: project1.name, + projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, + userId: 40051333, + initiatorUserId: 40051333, + })).should.be.true; + + done(); + }); + } + }); + }); + + it('sends send correct BUS API messages when link attachment deleted', (done) => { request(server) - .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) + .delete(`/v5/projects/${project1.id}/attachments/${attachments[1].id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -199,11 +332,11 @@ describe('Project Attachments delete', () => { createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, sinon.match({ resource: RESOURCES.ATTACHMENT, - id: attachment.id, + id: attachments[1].id, })).should.be.true; // Check Notification Service events - createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, sinon.match({ + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_ATTACHMENT_UPDATED, sinon.match({ projectId: project1.id, projectName: project1.name, projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, diff --git a/src/routes/attachments/download.js b/src/routes/attachments/get.js similarity index 56% rename from src/routes/attachments/download.js rename to src/routes/attachments/get.js index 3cc289f4..ef6e562a 100644 --- a/src/routes/attachments/download.js +++ b/src/routes/attachments/get.js @@ -3,19 +3,33 @@ import config from 'config'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; +import { ATTACHMENT_TYPES } from '../../constants'; /** - * API to download a project attachment. - * + * API to get a project attachment. */ const permissions = tcMiddleware.permissions; -const getFileDownloadUrl = (req, filePath) => { - if (process.env.NODE_ENV === 'development' && config.get('enableFileUpload') === 'false') { - return ['', 'dummy://url']; +/** + * This private function gets the pre-signed url if the attachment is a file + * + * @param {Object} req The http request + * @param {Object} attachment The project attachment object + * @returns {Array} The array of two promises, first one if the attachment object promise, + * The second promise is for the file pre-signed url (if attachment type is file) + */ +const getPreSignedUrl = (req, attachment) => { + if (attachment.type === ATTACHMENT_TYPES.LINK) { + // If the attachment is a link return it as-is without getting the pre-signed url + return [attachment, '']; + } // The attachment is a file + // In development/test mode, if file upload is disabled, we return the dummy attachment object + if (_.includes(['development', 'test'], process.env.NODE_ENV) && config.get('enableFileUpload') === 'false') { + return [attachment, 'dummy://url']; } - return util.getFileDownloadUrl(req, filePath); + // Not in development mode or file upload is not disabled + return [attachment, util.getFileDownloadUrl(req, attachment.path)]; }; module.exports = [ @@ -61,11 +75,7 @@ module.exports = [ err.status = 404; return Promise.reject(err); } - if (process.env.NODE_ENV === 'development' && config.get('enableFileUpload') === 'false') { - return ['dummy://url']; - } - - return getFileDownloadUrl(req, attachment.filePath); + return getPreSignedUrl(req, attachment); }) .catch((error) => { req.log.error('Error fetching attachment', error); @@ -76,12 +86,16 @@ module.exports = [ } req.log.debug('attachment found in ES'); const attachment = data[0].inner_hits.attachments.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle - return getFileDownloadUrl(req, attachment.filePath); + + return getPreSignedUrl(req, attachment); }) .then((result) => { - req.log.debug('getFileDownloadUrl result: ', JSON.stringify(result)); - const url = result[1]; - return res.json({ url }); + req.log.debug('getPresigned url result: ', JSON.stringify(result)); + if (_.isEmpty(result[1])) { + return res.json(result[0]); + } + + return res.json(_.extend(result[0], { url: result[1] })); }) .catch(next); }, diff --git a/src/routes/attachments/download.spec.js b/src/routes/attachments/get.spec.js similarity index 91% rename from src/routes/attachments/download.spec.js rename to src/routes/attachments/get.spec.js index 84ce6821..4b8f6f5b 100644 --- a/src/routes/attachments/download.spec.js +++ b/src/routes/attachments/get.spec.js @@ -6,19 +6,21 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import util from '../../util'; +import { ATTACHMENT_TYPES } from '../../constants'; -describe('Project Attachments download', () => { +describe('Get Project attachments Tests', () => { let project1; let attachment; let getFileDownloadUrlStub; before(() => { getFileDownloadUrlStub = sinon.stub(util, 'getFileDownloadUrl'); - getFileDownloadUrlStub.returns(['dummy://url']); + getFileDownloadUrlStub.returns('dummy://url'); }); beforeEach((done) => { testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => { models.Project.create({ type: 'generic', @@ -49,7 +51,9 @@ describe('Project Attachments download', () => { contentType: 'application/unknown', size: 12312, category: null, - filePath: 'https://media.topcoder.com/projects/1/test.txt', + path: 'https://media.topcoder.com/projects/1/test.txt', + type: ATTACHMENT_TYPES.FILE, + tags: ['tag1', 'tag2'], createdBy: testUtil.userIds.copilot, updatedBy: 1, allowedUsers: [testUtil.userIds.member], diff --git a/src/routes/attachments/list.spec.js b/src/routes/attachments/list.spec.js index 07a1f90e..52203c7a 100644 --- a/src/routes/attachments/list.spec.js +++ b/src/routes/attachments/list.spec.js @@ -5,6 +5,7 @@ import request from 'supertest'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; +import { ATTACHMENT_TYPES } from '../../constants'; const should = chai.should(); @@ -13,6 +14,7 @@ describe('Project Attachments download', () => { beforeEach((done) => { testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => { models.Project.create({ type: 'generic', @@ -43,17 +45,20 @@ describe('Project Attachments download', () => { contentType: 'application/unknown', size: 12312, category: null, - filePath: 'https://media.topcoder.com/projects/1/test.txt', + path: 'https://media.topcoder.com/projects/1/test.txt', + type: ATTACHMENT_TYPES.FILE, + tags: ['tag1', 'tag2'], createdBy: testUtil.userIds.copilot, updatedBy: 1, }, { projectId: project1.id, - title: 'test2.txt', - description: 'blah 2', - contentType: 'application/unknown', - size: 12312, + title: 'link test 1', + description: 'link test description', + size: 123456, category: null, - filePath: 'https://media.topcoder.com/projects/1/test2.txt', + path: 'https://media.topcoder.com/projects/1/test2.txt', + type: ATTACHMENT_TYPES.LINK, + tags: ['tag3'], createdBy: testUtil.userIds.copilot, updatedBy: 1, }]).then(() => done())); @@ -114,8 +119,23 @@ describe('Project Attachments download', () => { const resJson = res.body; resJson.should.have.length(2); resJson[0].description.should.be.eql('blah'); - resJson[0].filePath.should.be.eql('https://media.topcoder.com/projects/1/test.txt'); + resJson[0].path.should.be.eql('https://media.topcoder.com/projects/1/test.txt'); resJson[0].projectId.should.be.eql(project1.id); + resJson[0].type.should.be.eql(ATTACHMENT_TYPES.FILE); + resJson[0].createdBy.should.be.eql(testUtil.userIds.copilot); + resJson[0].updatedBy.should.be.eql(1); + should.exist(resJson[0].createdAt); + should.exist(resJson[0].updatedAt); + should.not.exist(resJson[0].deletedBy); + should.not.exist(resJson[0].deletedAt); + + resJson[1].projectId.should.be.eql(project1.id); + resJson[1].description.should.be.eql('link test description'); + resJson[1].path.should.be.eql('https://media.topcoder.com/projects/1/test2.txt'); + resJson[1].type.should.be.eql(ATTACHMENT_TYPES.LINK); + resJson[1].tags.should.be.eql(['tag3']); + resJson[1].createdBy.should.be.eql(testUtil.userIds.copilot); + resJson[1].updatedBy.should.be.eql(1); should.exist(resJson[0].createdAt); should.exist(resJson[0].updatedAt); should.not.exist(resJson[0].deletedBy); diff --git a/src/routes/attachments/update.js b/src/routes/attachments/update.js index 54cc74d2..b52b9960 100644 --- a/src/routes/attachments/update.js +++ b/src/routes/attachments/update.js @@ -19,6 +19,8 @@ const updateProjectAttachmentValidation = { title: Joi.string().required(), description: Joi.string().optional().allow(null).allow(''), allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null), + tags: Joi.array().items(Joi.string().min(1)).optional(), + path: Joi.string(), }), }; diff --git a/src/routes/attachments/update.spec.js b/src/routes/attachments/update.spec.js index 34957a42..80050d79 100644 --- a/src/routes/attachments/update.spec.js +++ b/src/routes/attachments/update.spec.js @@ -7,13 +7,14 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT, ATTACHMENT_TYPES } from '../../constants'; const should = chai.should(); describe('Project Attachments update', () => { let project1; let attachment; + let link; beforeEach((done) => { testUtil.clearDb() .then(() => { @@ -46,13 +47,30 @@ describe('Project Attachments update', () => { contentType: 'application/unknown', size: 12312, category: null, - filePath: 'https://media.topcoder.com/projects/1/test.txt', + path: 'https://media.topcoder.com/projects/1/test.txt', + type: ATTACHMENT_TYPES.FILE, + tags: ['tag1', 'tag2', 'tag3'], createdBy: testUtil.userIds.copilot, updatedBy: 1, allowedUsers: [], }).then((a1) => { attachment = a1; - done(); + models.ProjectAttachment.create( + { + projectId: project1.id, + title: 'Test Link 1', + description: 'Test link 1 description', + size: 123456, + category: null, + path: 'https://connect.topcoder-dev.com/projects/8600/assets', + type: ATTACHMENT_TYPES.LINK, + tags: ['tag3', 'tag4'], + createdBy: testUtil.userIds.copilot, + updatedBy: 1, + }).then((_link) => { + link = _link; + done(); + }); })); }); }); @@ -91,13 +109,18 @@ describe('Project Attachments update', () => { .expect(404, done); }); - it('should return 200 if attachment was successfully updated', (done) => { + it('should return 200 if file attachment was successfully updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ title: 'updated title', description: 'updated description' }) + .send({ + title: 'updated title', + description: 'updated description', + tags: ['updatedTag'], + allowedUsers: [123, 521], + }) .expect(200) .end((err, res) => { if (err) { @@ -107,6 +130,36 @@ describe('Project Attachments update', () => { should.exist(resJson); resJson.title.should.equal('updated title'); resJson.description.should.equal('updated description'); + resJson.tags.should.eql(['updatedTag']); + resJson.allowedUsers.should.eql([123, 521]); + done(); + } + }); + }); + + it('should return 200 if link attachment was successfully updated', (done) => { + request(server) + .patch(`/v5/projects/${project1.id}/attachments/${link.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + title: 'updated link title', + description: 'updated link description', + tags: ['linkTag1', 'linkTag2'], + allowedUsers: [1111, 2222], + }) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.title.should.equal('updated link title'); + resJson.description.should.equal('updated link description'); + resJson.tags.should.eql(['linkTag1', 'linkTag2']); + resJson.allowedUsers.should.eql([1111, 2222]); done(); } }); @@ -168,7 +221,7 @@ describe('Project Attachments update', () => { })).should.be.true; // Check Notification Service events - createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, sinon.match({ + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_ATTACHMENT_UPDATED, sinon.match({ projectId: project1.id, projectName: project1.name, projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, diff --git a/src/routes/index.js b/src/routes/index.js index 361e4738..605b9699 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -139,7 +139,7 @@ router.route('/v5/projects/:projectId(\\d+)/attachments') .get(require('./attachments/list')); router.route('/v5/projects/:projectId(\\d+)/attachments/:id(\\d+)') - .get(require('./attachments/download')) + .get(require('./attachments/get')) .patch(require('./attachments/update')) .delete(require('./attachments/delete')); @@ -229,13 +229,14 @@ router.route('/v5/timelines/metadata/milestoneTemplates/:milestoneTemplateId(\\d .patch(require('./milestoneTemplates/update')) .delete(require('./milestoneTemplates/delete')); -router.route('/v5/projects/:projectId(\\d+)/members/invite') - .post(require('./projectMemberInvites/create')) - .put(require('./projectMemberInvites/update')) - .get(require('./projectMemberInvites/get')); +router.route('/v5/projects/:projectId(\\d+)/invites') + .get(require('./projectMemberInvites/list')) + .post(require('./projectMemberInvites/create')); -router.route('/v5/projects/:projectId(\\d+)/members/invites') - .get(require('./projectMemberInvites/list')); +router.route('/v5/projects/:projectId(\\d+)/invites/:inviteId(\\d+)') + .patch(require('./projectMemberInvites/update')) + .get(require('./projectMemberInvites/get')) + .delete(require('./projectMemberInvites/delete')); router.route('/v5/projects/metadata/orgConfig') .post(require('./orgConfig/create')); diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index d7b3bfbf..fe8e1897 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -22,7 +22,7 @@ const permissions = tcMiddleware.permissions; const addMemberValidations = { body: Joi.object().keys({ - userIds: Joi.array().items(Joi.number()).optional().min(1), + handles: Joi.array().items(Joi.string()).optional().min(1), emails: Joi.array().items(Joi.string().email()).optional().min(1), role: Joi.any().valid(_.values(PROJECT_MEMBER_ROLE)).required(), }).required(), @@ -55,34 +55,44 @@ const compareEmail = (email1, email2, options = { UNIQUE_GMAIL_VALIDATION: false return _.toLower(email1) === _.toLower(email2); }; +/** + * Get user handle by user id from user list. Used to generate error messages below. + * You need to make sure user with specific userId exists in users. + * @param {Number} userId user id + * @param {Array} users user list + * @returns {String} user handle + */ +const getUserHandleById = (userId, users) => _.find(users, { userId }).handle; + /** * Helper method to build promises for creating new invites in DB * * @param {Object} req express request object - * @param {Object} invite invite to process + * @param {Array} inviteEmails invite.emails + * @param {Array} inviteUserIds filtered invite.userIds * @param {Array} invites existent invites from DB * @param {Object} data template for new invites to be put in DB * @param {Array} failed failed invites error message * @param {Array} members already members of the project - * + * @param {Array} inviteUsers users retrieved by invite.handles * @returns {Promise} list of promises */ -const buildCreateInvitePromises = (req, invite, invites, data, failed, members) => { +const buildCreateInvitePromises = (req, inviteEmails, inviteUserIds, invites, data, failed, members, inviteUsers) => { const invitePromises = []; - if (invite.userIds) { + if (inviteUserIds) { // remove invites for users that are invited already const errMessageForAlreadyInvitedUsers = 'User with such handle is already invited to this project.'; - _.remove(invite.userIds, u => _.some(invites, (i) => { + _.remove(inviteUserIds, u => _.some(invites, (i) => { const isPresent = i.userId === u; if (isPresent) { failed.push(_.assign({}, { - userId: u, + handle: getUserHandleById(u, inviteUsers), message: errMessageForAlreadyInvitedUsers, })); } return isPresent; })); - invite.userIds.forEach((userId) => { + inviteUserIds.forEach((userId) => { const dataNew = _.clone(data); dataNew.userId = userId; @@ -91,10 +101,10 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed, members) }); } - if (invite.emails) { + if (inviteEmails) { // if for some emails there are already existent users, we will invite them by userId, // to avoid sending them registration email - return util.lookupMultipleUserEmails(req, invite.emails, MAX_PARALLEL_REQUEST_QTY) + return util.lookupMultipleUserEmails(req, inviteEmails, MAX_PARALLEL_REQUEST_QTY) .then((existentUsers) => { // existent user we will invite by userId and email const existentUsersWithNumberId = existentUsers.map((user) => { @@ -105,7 +115,7 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed, members) return userWithNumberId; }); // non-existent users we will invite them by email only - const nonExistentUserEmails = invite.emails.filter(inviteEmail => + const nonExistentUserEmails = inviteEmails.filter(inviteEmail => !_.find(existentUsers, existentUser => compareEmail(existentUser.email, inviteEmail, { UNIQUE_GMAIL_VALIDATION: false })), ); @@ -171,7 +181,7 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed, members) return invitePromises; }).catch((error) => { req.log.error(error); - _.forEach(invite.emails, email => failed.push(_.assign({}, { email, message: error.statusText }))); + _.forEach(inviteEmails, email => failed.push(_.assign({}, { email, message: error.statusText }))); return invitePromises; }); } @@ -247,8 +257,8 @@ module.exports = [ return next(err); } - if (!invite.userIds && !invite.emails) { - const err = new Error('Either userIds or emails are required'); + if (!invite.handles && !invite.emails) { + const err = new Error('Either handles or emails are required'); err.status = 400; return next(err); } @@ -259,120 +269,134 @@ module.exports = [ return next(err); } - const members = req.context.currentProjectMembers; - const projectId = _.parseInt(req.params.projectId); - - const promises = []; - const errorMessageForAlreadyMemberUser = 'User with such handle is already a member of the team.'; - if (invite.userIds) { - // remove members already in the team - _.remove(invite.userIds, u => _.some(members, (m) => { - const isPresent = m.userId === u; - if (isPresent) { - failed.push(_.assign({}, { - userId: m.userId, - message: errorMessageForAlreadyMemberUser, - })); - } - return isPresent; - })); + // get member details by handles first + return util.getMemberDetailsByHandles(invite.handles, req.log, req.id).then((inviteUsers) => { + const members = req.context.currentProjectMembers; + const projectId = _.parseInt(req.params.projectId); + // check user handle exists in returned result + const errorMessageHandleNotExist = 'User with such handle does not exist'; + if (!!invite.handles && invite.handles.length > 0) { + const existentHandles = _.map(inviteUsers, 'handle'); + failed = _.concat(failed, _.map(_.difference(invite.handles, existentHandles), handle => _.assign({}, { + handle, + message: errorMessageHandleNotExist, + }))); + } + + let inviteUserIds = _.map(inviteUsers, 'userId'); + const promises = []; + const errorMessageForAlreadyMemberUser = 'User with such handle is already a member of the team.'; + + if (inviteUserIds) { + // remove members already in the team + _.remove(inviteUserIds, u => _.some(members, (m) => { + const isPresent = m.userId === u; + if (isPresent) { + failed.push(_.assign({}, { + handle: getUserHandleById(m.userId, inviteUsers), + message: errorMessageForAlreadyMemberUser, + })); + } + return isPresent; + })); // permission: // user has to have constants.MANAGER_ROLES role // to be invited as PROJECT_MEMBER_ROLE.MANAGER - if (_.includes(PROJECT_MEMBER_MANAGER_ROLES, invite.role)) { - _.forEach(invite.userIds, (userId) => { - req.log.info(userId); - promises.push(util.getUserRoles(userId, req.log, req.id)); - }); + if (_.includes(PROJECT_MEMBER_MANAGER_ROLES, invite.role)) { + _.forEach(inviteUserIds, (userId) => { + req.log.info(userId); + promises.push(util.getUserRoles(userId, req.log, req.id)); + }); + } } - } - if (invite.emails) { + if (invite.emails) { // email invites can only be used for CUSTOMER role - if (invite.role !== PROJECT_MEMBER_ROLE.CUSTOMER) { // eslint-disable-line no-lonely-if - const message = `Emails can only be used for ${PROJECT_MEMBER_ROLE.CUSTOMER}`; - failed = _.concat(failed, _.map(invite.emails, email => _.assign({}, { email, message }))); - delete invite.emails; - } - } - if (promises.length === 0) { - promises.push(Promise.resolve()); - } - return Promise.all(promises).then((rolesList) => { - if (!!invite.userIds && _.includes(PROJECT_MEMBER_MANAGER_ROLES, invite.role)) { - req.log.debug('Checking if userId is allowed as manager'); - const forbidUserList = []; - _.zip(invite.userIds, rolesList).forEach((data) => { - const [userId, roles] = data; - req.log.debug(roles); - - if (roles && !util.hasIntersection(MANAGER_ROLES, roles)) { - forbidUserList.push(userId); - } - }); - if (forbidUserList.length > 0) { - const message = 'cannot be added with a Manager role to the project'; - failed = _.concat(failed, _.map(forbidUserList, id => _.assign({}, { userId: id, message }))); - invite.userIds = _.filter(invite.userIds, userId => !_.includes(forbidUserList, userId)); + if (invite.role !== PROJECT_MEMBER_ROLE.CUSTOMER) { // eslint-disable-line no-lonely-if + const message = `Emails can only be used for ${PROJECT_MEMBER_ROLE.CUSTOMER}`; + failed = _.concat(failed, _.map(invite.emails, email => _.assign({}, { email, message }))); + delete invite.emails; } } - return models.ProjectMemberInvite.getPendingInvitesForProject(projectId) - .then((invites) => { - const data = { - projectId, - role: invite.role, - // invite directly if user is admin or copilot manager - status: (invite.role !== PROJECT_MEMBER_ROLE.COPILOT || - util.hasRoles(req, [USER_ROLE.CONNECT_ADMIN, USER_ROLE.COPILOT_MANAGER])) - ? INVITE_STATUS.PENDING - : INVITE_STATUS.REQUESTED, - createdBy: req.authUser.userId, - updatedBy: req.authUser.userId, - }; - - req.log.debug('Creating invites'); - return models.Sequelize.Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed, members)) - .then((values) => { - values.forEach((v) => { - // emit the event - util.sendResourceToKafkaBus( - req, - EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, - RESOURCES.PROJECT_MEMBER_INVITE, - v.toJSON()); - - req.app.services.pubsub.publish( - EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, - v, - { correlationId: req.id }, - ); - // send email invite (async) - if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) { - sendInviteEmail(req, projectId, v); - } - }); - return values.map(value => value.get({ plain: true })); - }); // models.sequelize.Promise.all - }); // models.ProjectMemberInvite.getPendingInvitesForProject - }) - .then(values => ( - // populate successful invites with user details if required - util.getObjectsWithMemberDetails(values, fields, req) - .catch((err) => { - req.log.error('Cannot get user details for invites.'); - req.log.debug('Error during getting user details for invites', err); - // continues without details anyway - return values; - }) - )) - .then((values) => { - const success = _.assign({}, { success: values }); - if (failed.length) { - res.status(403).json(_.assign({}, util.maskInviteEmails('$.success[?(@.email)]', success, req), { failed })); - } else { - res.status(201).json(util.maskInviteEmails('$.success[?(@.email)]', success, req)); + if (promises.length === 0) { + promises.push(Promise.resolve()); } - }) - .catch(err => next(err)); + return Promise.all(promises).then((rolesList) => { + if (!!inviteUserIds && _.includes(PROJECT_MEMBER_MANAGER_ROLES, invite.role)) { + req.log.debug('Checking if userId is allowed as manager'); + const forbidUserList = []; + _.zip(inviteUserIds, rolesList).forEach((data) => { + const [userId, roles] = data; + req.log.debug(roles); + + if (roles && !util.hasIntersection(MANAGER_ROLES, roles)) { + forbidUserList.push(userId); + } + }); + if (forbidUserList.length > 0) { + const message = 'cannot be added with a Manager role to the project'; + failed = _.concat(failed, _.map(forbidUserList, + id => _.assign({}, { handle: getUserHandleById(id, inviteUsers), message }))); + inviteUserIds = _.filter(inviteUserIds, userId => !_.includes(forbidUserList, userId)); + } + } + return models.ProjectMemberInvite.getPendingInvitesForProject(projectId) + .then((invites) => { + const data = { + projectId, + role: invite.role, + // invite directly if user is admin or copilot manager + status: (invite.role !== PROJECT_MEMBER_ROLE.COPILOT || + util.hasRoles(req, [USER_ROLE.CONNECT_ADMIN, USER_ROLE.COPILOT_MANAGER])) + ? INVITE_STATUS.PENDING + : INVITE_STATUS.REQUESTED, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }; + req.log.debug('Creating invites'); + return models.Sequelize.Promise.all(buildCreateInvitePromises( + req, invite.emails, inviteUserIds, invites, data, failed, members, inviteUsers)) + .then((values) => { + values.forEach((v) => { + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, + RESOURCES.PROJECT_MEMBER_INVITE, + v.toJSON()); + + req.app.services.pubsub.publish( + EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, + v, + { correlationId: req.id }, + ); + // send email invite (async) + if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) { + sendInviteEmail(req, projectId, v); + } + }); + return values.map(value => value.get({ plain: true })); + }); // models.sequelize.Promise.all + }); // models.ProjectMemberInvite.getPendingInvitesForProject + }) + .then(values => ( + // populate successful invites with user details if required + util.getObjectsWithMemberDetails(values, fields, req) + .catch((err) => { + req.log.error('Cannot get user details for invites.'); + req.log.debug('Error during getting user details for invites', err); + // continues without details anyway + return values; + }) + )) + .then((values) => { + const response = _.assign({}, { success: util.postProcessInvites('$[*]', values, req) }); + if (failed.length) { + res.status(403).json(_.assign({}, response, { failed })); + } else { + res.status(201).json(response); + } + }); + }).catch(err => next(err)); }, ]; diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index ac77d1cc..3cc20468 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -154,7 +154,7 @@ describe('Project Member Invite create', () => { testUtil.clearDb(done); }); - describe('POST /projects/{id}/members/invite', () => { + describe('POST /projects/{id}/invites', () => { let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); @@ -171,6 +171,46 @@ describe('Project Member Invite create', () => { firstName: 'Admin', lastName: 'User', }])); + // mock getMemberDetailsByHandles function. + sandbox.stub(util, 'getMemberDetailsByHandles', (handles) => { + if (_.isNil(handles) || _.isEmpty(handles)) { + return Promise.resolve([]); + } + let userHandles = [{ + userId: 40011578, + handle: 'magrathean', + }, { + userId: 40011579, + handle: 'test_user1', + }, { + userId: 40011578, + handle: 'test_user2', + }, { + userId: 40051331, + handle: 'test_customer1', + }, { + userId: 40051332, + handle: 'test_copilot1', + }, { + userId: 40051333, + handle: 'test_manager1', + }, { + userId: 40051334, + handle: 'test_manager2', + }, { + userId: 40051335, + handle: 'test_manager3', + }, { + userId: 40051336, + handle: 'test_manager4', + }, { + userId: 40135978, + handle: 'test_admin1', + }]; + userHandles = _.each(userHandles, u => _.extend(u, { firstName: 'Connect', lastName: 'User' })); + + return Promise.resolve(_.filter(userHandles, u => handles.indexOf(u.handle) >= 0)); + }); }); afterEach(() => { sandbox.restore(); @@ -179,12 +219,12 @@ describe('Project Member Invite create', () => { it('should return 201 if userIds and emails are presented the same time', (done) => { request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - userIds: [40051331], + handles: ['test_customer1'], emails: ['hello@world.com'], role: 'customer', }) @@ -205,10 +245,10 @@ describe('Project Member Invite create', () => { }); }); - it('should return 400 if neither userIds or email is presented', + it('should return 400 if neither handles or email is presented', (done) => { request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -222,7 +262,7 @@ describe('Project Member Invite create', () => { done(err); } else { const errorMessage = _.get(res.body, 'message', ''); - sinon.assert.match(errorMessage, /.*Either userIds or emails are required/); + sinon.assert.match(errorMessage, /.*Either handles or emails are required/); done(); } }); @@ -247,12 +287,12 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v5/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - userIds: [40152855], + handles: ['test_user1'], role: 'copilot', }) .expect('Content-Type', /json/) @@ -287,12 +327,12 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v5/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - userIds: [40152855], + handles: ['test_user1'], role: 'copilot', }) .expect('Content-Type', /json/) @@ -329,7 +369,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v5/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -347,8 +387,6 @@ describe('Project Member Invite create', () => { should.exist(resJson); resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); - // temporary disable this feature, because it has some side effects - // resJson.email.should.equal('he**o@wo**d.com'); // email is masked resJson.email.should.equal('hello@world.com'); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); @@ -382,7 +420,7 @@ describe('Project Member Invite create', () => { email: 'hello@world.com', }])); request(server) - .post(`/v5/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -400,9 +438,7 @@ describe('Project Member Invite create', () => { should.exist(resJson); resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); - resJson.userId.should.equal(12345); - // temporary disable this feature, because it has some side effects - // resJson.email.should.equal('he**o@wo**d.com'); // email is masked + should.not.exist(resJson.userId); resJson.email.should.equal('hello@world.com'); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); @@ -422,6 +458,7 @@ describe('Project Member Invite create', () => { status: 200, content: { success: [{ + userId: 40152855, roleName: USER_ROLE.COPILOT, }], }, @@ -431,12 +468,12 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v5/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - userIds: [40152855], + handles: ['test_customer1'], role: 'customer', }) .expect('Content-Type', /json/) @@ -449,32 +486,22 @@ describe('Project Member Invite create', () => { should.exist(resJson); resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); - resJson.userId.should.equal(40152855); + resJson.userId.should.equal(40051331); + should.not.exist(resJson.email); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); } }); }); - it('should return 403 and failed list when trying add already team member by userId', (done) => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - get: () => Promise.resolve({ - status: 200, - data: { - success: [{ - roleName: USER_ROLE.COPILOT, - }], - }, - }), - }); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + it('should return 403 and failed list when trying add already team member by handle', (done) => { request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - userIds: [40158431], + handles: ['test_copilot1'], role: 'customer', }) .expect('Content-Type', /json/) @@ -485,7 +512,7 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); - resJson[0].userId.should.equal(40158431); + resJson[0].handle.should.equal('test_copilot1'); resJson[0].message.should.equal('User with such handle is already a member of the team.'); resJson.length.should.equal(1); server.services.pubsub.publish.neverCalledWith('project.member.invite.created').should.be.true; @@ -500,6 +527,7 @@ describe('Project Member Invite create', () => { status: 200, data: { success: [{ + userId: 40158431, roleName: USER_ROLE.COPILOT, }], }, @@ -512,7 +540,7 @@ describe('Project Member Invite create', () => { email: 'romit.choudhary@rivigo.com', }])); request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -537,33 +565,14 @@ describe('Project Member Invite create', () => { }); }); - it('should return 403 and failed list when trying add already invited member by userId', (done) => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - get: () => Promise.resolve({ - status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: { - success: [{ - roleName: USER_ROLE.COPILOT, - }], - }, - }, - }, - }), - }); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + it('should return 403 and failed list when trying add already invited member by handle', (done) => { request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - userIds: [40051335], + handles: ['test_manager3'], role: 'customer', }) .expect('Content-Type', /json/) @@ -575,7 +584,7 @@ describe('Project Member Invite create', () => { const resJson = res.body.failed; should.exist(resJson); resJson.length.should.equal(1); - resJson[0].userId.should.equal(40051335); + resJson[0].handle.should.equal('test_manager3'); resJson[0].message.should.equal('User with such handle is already invited to this project.'); server.services.pubsub.publish.neverCalledWith('project.member.invite.created').should.be.true; done(); @@ -614,12 +623,12 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - userIds: [40152855], + handles: ['test_user'], role: 'manager', }) .expect('Content-Type', /json/) @@ -637,16 +646,43 @@ describe('Project Member Invite create', () => { }); }); + it('should return 201 if try to create invitation with non-existent handle', (done) => { + util.getUserRoles.restore(); + sandbox.stub(util, 'getUserRoles', () => Promise.resolve([USER_ROLE.MANAGER])); + request(server) + .post(`/v5/projects/${project1.id}/invites`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + handles: ['invalid_handle'], + role: 'customer', + }) + .expect('Content-Type', /json/) + .expect(403) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.failed[0]; + should.exist(resJson); + resJson.handle.should.equal('invalid_handle'); + resJson.message.should.equal('User with such handle does not exist'); + done(); + } + }); + }); + it('should return 201 if try to create manager with MANAGER_ROLES', (done) => { util.getUserRoles.restore(); sandbox.stub(util, 'getUserRoles', () => Promise.resolve([USER_ROLE.MANAGER])); request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - userIds: [40152855], + handles: ['test_manager4'], role: 'manager', }) .expect('Content-Type', /json/) @@ -656,7 +692,7 @@ describe('Project Member Invite create', () => { should.exist(resJson); resJson.role.should.equal('manager'); resJson.projectId.should.equal(project1.id); - resJson.userId.should.equal(40152855); + resJson.userId.should.equal(40051336); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); }); @@ -666,12 +702,12 @@ describe('Project Member Invite create', () => { util.getUserRoles.restore(); sandbox.stub(util, 'getUserRoles', () => Promise.resolve([USER_ROLE.MANAGER])); request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - userIds: [40152855], + handles: ['test_manager4'], role: 'account_manager', }) .expect('Content-Type', /json/) @@ -681,7 +717,7 @@ describe('Project Member Invite create', () => { should.exist(resJson); resJson.role.should.equal('account_manager'); resJson.projectId.should.equal(project1.id); - resJson.userId.should.equal(40152855); + resJson.userId.should.equal(40051336); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); }); @@ -691,12 +727,12 @@ describe('Project Member Invite create', () => { util.getUserRoles.restore(); sandbox.stub(util, 'getUserRoles', () => Promise.resolve(['Topcoder User'])); request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - userIds: [40152855], + handles: ['test_customer1'], role: 'account_manager', }) .expect('Content-Type', /json/) @@ -715,32 +751,13 @@ describe('Project Member Invite create', () => { }); it('should return 201 if try to create customer with COPILOT', (done) => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - get: () => Promise.resolve({ - status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: { - success: [{ - roleName: USER_ROLE.COPILOT, - }], - }, - }, - }, - }), - }); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - userIds: [40051331], + handles: ['test_customer1'], role: 'copilot', }) .expect('Content-Type', /json/) @@ -762,7 +779,7 @@ describe('Project Member Invite create', () => { it('should return 403 and failed list when trying add already invited member by lowercase email', (done) => { request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -788,7 +805,7 @@ describe('Project Member Invite create', () => { it('should return 403 and failed list when trying add already invited member by uppercase email', (done) => { request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -804,7 +821,7 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); - resJson[0].email.should.equal('DUPLICATE_UPPERCASE@test.com'); // email is masked + resJson[0].email.should.equal('DUPLICATE_UPPERCASE@test.com'); resJson[0].message.should.equal('User with such email is already invited to this project.'); resJson.length.should.equal(1); done(); @@ -815,7 +832,7 @@ describe('Project Member Invite create', () => { xit('should return 403 and failed list when trying add already invited member by gmail email with dot', (done) => { request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -841,7 +858,7 @@ describe('Project Member Invite create', () => { xit('should return 403 and failed list when trying add already invited member by gmail email without dot', (done) => { request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -876,23 +893,14 @@ describe('Project Member Invite create', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('should send correct BUS API messages when invite added by userId', (done) => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - get: () => Promise.resolve({ - status: 200, - data: [{ - roleName: USER_ROLE.MANAGER, - }], - }), - }); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + it('should send correct BUS API messages when invite added by handle', (done) => { request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - userIds: [3], + handles: ['test_user2'], role: PROJECT_MEMBER_ROLE.CUSTOMER, }) .expect(201) @@ -906,14 +914,14 @@ describe('Project Member Invite create', () => { createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE, projectId: project1.id, - userId: 3, + userId: 40011578, email: null, })).should.be.true; // Check Notification Service events createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ projectId: project1.id, - userId: 3, + userId: 40011578, email: null, isSSO: false, })).should.be.true; @@ -935,7 +943,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -951,6 +959,10 @@ describe('Project Member Invite create', () => { testUtil.wait(() => { createEventSpy.callCount.should.be.eql(3); + createEventSpy.getCalls().forEach((call) => { + console.log(call.args) // eslint-disable-line + }); + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE, projectId: project1.id, diff --git a/src/routes/projectMemberInvites/delete.js b/src/routes/projectMemberInvites/delete.js new file mode 100644 index 00000000..b504d578 --- /dev/null +++ b/src/routes/projectMemberInvites/delete.js @@ -0,0 +1,83 @@ +import _ from 'lodash'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; +import { PROJECT_MEMBER_ROLE, INVITE_STATUS, EVENT, RESOURCES } from '../../constants'; +import { PERMISSION } from '../../permissions/constants'; + +/** + * API to delete invite member to project. + * + */ +const permissions = tcMiddleware.permissions; + +module.exports = [ + permissions('projectMemberInvite.delete'), + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const inviteId = _.parseInt(req.params.inviteId); + const email = req.authUser.email; + const currentUserId = req.authUser.userId; + + // get invite by id and project id + return models.ProjectMemberInvite.getPendingOrRequestedProjectInviteById(projectId, inviteId) + .then((invite) => { + // if invite doesn't exist, return 404 + if (!invite) { + const err = new Error(`invite not found for project id ${projectId}, inviteId ${inviteId},` + + ` email ${email} and userId ${currentUserId}`, + ); + err.status = 404; + return next(err); + } + // check this invitation is for logged-in user or not + const ownInvite = (!!invite && (invite.userId === currentUserId || invite.email === email)); + + // check permission + req.log.debug('Checking user permission for deleting invite'); + let error = null; + + if ( + invite.status === INVITE_STATUS.REQUESTED + && !util.hasPermission(PERMISSION.DELETE_REQUESTED_INVITE, req.authUser, req.context.currentProjectMembers) + ) { + error = 'You don\'t have permissions to cancel requested invites.'; + } else if ( + invite.role !== PROJECT_MEMBER_ROLE.CUSTOMER + && !ownInvite + && !util.hasPermission(PERMISSION.DELETE_NON_CUSTOMER_INVITE, req.authUser, req.context.currentProjectMembers) + ) { + error = 'You don\'t have permissions to cancel invites to Topcoder Team for other users.'; + } else if ( + invite.role === PROJECT_MEMBER_ROLE.CUSTOMER + && !ownInvite + && !util.hasPermission(PERMISSION.DELETE_CUSTOMER_INVITE, req.authUser, req.context.currentProjectMembers) + ) { + error = 'You don\'t have permissions to cancel invites to Customer Team for other users.'; + } + + if (error) { + const err = new Error(error); + err.status = 403; + return next(err); + } + + req.log.debug('Deleting (canceling) invite'); + return invite + .update({ + status: INVITE_STATUS.CANCELED, + }) + .then((updatedInvite) => { + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_REMOVED, + RESOURCES.PROJECT_MEMBER_INVITE, + updatedInvite.toJSON()); + + res.status(204).end(); + }); + }) + .catch(next); + }, +]; diff --git a/src/routes/projectMemberInvites/delete.spec.js b/src/routes/projectMemberInvites/delete.spec.js new file mode 100644 index 00000000..da337807 --- /dev/null +++ b/src/routes/projectMemberInvites/delete.spec.js @@ -0,0 +1,395 @@ +/* eslint-disable no-unused-expressions */ +import _ from 'lodash'; +import request from 'supertest'; +import sinon from 'sinon'; +import chai from 'chai'; +import models from '../../models'; +import server from '../../app'; +import util from '../../util'; +import testUtil from '../../tests/util'; +import busApi from '../../services/busApi'; +import { + BUS_API_EVENT, + RESOURCES, + PROJECT_MEMBER_ROLE, + INVITE_STATUS, +} from '../../constants'; + +const should = chai.should(); + +describe('Project member invite delete', () => { + let project1; + let project2; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + const p1 = models.Project.create({ + type: 'generic', + directProjectId: 1, + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then((p) => { + project1 = p; + // create members + const pm1 = models.ProjectMember.create({ + userId: testUtil.userIds.manager, + projectId: project1.id, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite1 = models.ProjectMemberInvite.create({ + id: 1, + projectId: project1.id, + userId: testUtil.userIds.member, + email: null, + role: PROJECT_MEMBER_ROLE.CUSTOMER, + status: INVITE_STATUS.PENDING, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite2 = models.ProjectMemberInvite.create({ + id: 2, + projectId: project1.id, + userId: testUtil.userIds.copilot, + email: null, + role: PROJECT_MEMBER_ROLE.COPILOT, + status: INVITE_STATUS.REQUESTED, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite3 = models.ProjectMemberInvite.create({ + id: 3, + projectId: project1.id, + userId: testUtil.userIds.manager, + email: null, + role: PROJECT_MEMBER_ROLE.MANAGER, + status: INVITE_STATUS.PENDING, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + return Promise.all([pm1, invite1, invite2, invite3]); + }); + + const p2 = models.Project.create({ + type: 'generic', + directProjectId: 1, + billingAccountId: 1, + name: 'test2', + description: 'test project2', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then((p) => { + project2 = p; + // create members + const pm = models.ProjectMember.create({ + userId: testUtil.userIds.manager, + projectId: project2.id, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite4 = models.ProjectMemberInvite.create({ + id: 4, + projectId: project2.id, + userId: testUtil.userIds.member, + email: null, + role: PROJECT_MEMBER_ROLE.CUSTOMER, + status: INVITE_STATUS.PENDING, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite5 = models.ProjectMemberInvite.create({ + id: 5, + projectId: project2.id, + userId: null, + email: 'romit.choudhary@rivigo.com', + role: PROJECT_MEMBER_ROLE.CUSTOMER, + status: INVITE_STATUS.PENDING, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite6 = models.ProjectMemberInvite.create({ + id: 6, + projectId: project2.id, + userId: testUtil.userIds.copilot, + email: null, + role: PROJECT_MEMBER_ROLE.COPILOT, + status: INVITE_STATUS.ACCEPTED, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + return Promise.all([pm, invite4, invite5, invite6]); + }); + + Promise.all([p1, p2]).then(() => done()); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('DELETE /projects/{id}/invites', () => { + const body = { + status: 'accepted', + }; + + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('should return 403 if user does not have permissions', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/invites/1`) + .send(body) + .expect(403, done); + }); + + it('should return 404 if invitation id and project id doesn\'t match', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/invites/5`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(404) + .end(() => { + done(); + }); + }); + + it('should return 404 if project id doesn\'t exist', (done) => { + request(server) + .delete('/v5/projects/99999/invites/1') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(404) + .end(() => { + done(); + }); + }); + + it('should return 404 if invitation id doesn\'t exist', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/invites/99999`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(404) + .end(() => { + done(); + }); + }); + + it('should return 404 if invitation status is not pending or requested', (done) => { + request(server) + .delete(`/v5/projects/${project2.id}/invites/6`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(404) + .end(() => { + done(); + }); + }); + + it('should return 403 if try to cancel MANAGER role invite with copilot', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/invites/3`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect('Content-Type', /json/) + .expect(403) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + const errorMessage = _.get(resJson, 'message', ''); + sinon.assert.match(errorMessage, + 'You don\'t have permissions to cancel invites to Topcoder Team for other users.'); + done(); + } + }); + }); + + it('should return 403 if try to cancel others Topcoder Team invite with CUSTOMER', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/invites/3`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(403) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + const errorMessage = _.get(resJson, 'message', ''); + sinon.assert.match(errorMessage, + 'You don\'t have permissions to cancel invites to Topcoder Team for other users.'); + done(); + } + }); + }); + + it('should return 403 if try to cancel COPILOT role invite with copilot', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/invites/2`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect('Content-Type', /json/) + .expect(403) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + const errorMessage = _.get(resJson, 'message', ''); + sinon.assert.match(errorMessage, 'You don\'t have permissions to cancel requested invites.'); + done(); + } + }); + }); + + it('should return 204 if member cancels his/her invitation', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/invites/1`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(204) + .end(() => done()); + }); + + it('should return 204 if admin cancels his/her invitation', (done) => { + request(server) + .delete(`/v5/projects/${project2.id}/invites/4`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end(() => done()); + }); + + it('should return 204 if copilot cancels his/her invitation', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/invites/2`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(204) + .end(() => done()); + }); + + it('should return 204 if user cancels invitation', (done) => { + request(server) + .delete(`/v5/projects/${project1.id}/invites/5`) + .set({ + Authorization: `Bearer ${testUtil.jwts.romit}`, + }) + .expect(204) + .end(() => done()); + }); + + describe('Bus api', () => { + let createEventSpy; + + before((done) => { + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + testUtil.wait(done); + }); + + beforeEach(() => { + createEventSpy = sandbox.spy(busApi, 'createEvent'); + }); + + it('should send correct BUS API messages when invite is accepted', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + get: () => Promise.resolve({ + status: 200, + data: {}, + }), + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .delete(`/v5/projects/${project1.id}/invites/3`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(204) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(1); + + // Events for accepted invite + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REMOVED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER_INVITE, + projectId: project1.id, + id: 3, + })).should.be.true; + + done(); + }); + } + }); + }); + }); + }); +}); diff --git a/src/routes/projectMemberInvites/get.js b/src/routes/projectMemberInvites/get.js index 2d38ce36..05552f82 100644 --- a/src/routes/projectMemberInvites/get.js +++ b/src/routes/projectMemberInvites/get.js @@ -26,19 +26,12 @@ module.exports = [ permissions('projectMemberInvite.get'), (req, res, next) => { const projectId = _.parseInt(req.params.projectId); + const inviteId = _.parseInt(req.params.inviteId); const currentUserId = req.authUser.userId; const email = req.authUser.email; const fields = req.query.fields ? req.query.fields.split(',') : null; - try { - util.validateFields(fields, ALLOWED_FIELDS); - } catch (validationError) { - const err = new Error(`"fields" is not valid: ${validationError.message}`); - err.status = 400; - return next(err); - } - - return util.fetchByIdFromES('invites', { + const esSearchParam = { query: { nested: { path: 'invites', @@ -49,16 +42,7 @@ module.exports = [ bool: { must: [ { term: { 'invites.projectId': projectId } }, - { - bool: { - should: [ - { term: { 'invites.email': email } }, - { term: { 'invites.userId': currentUserId } }, - ], - minimum_number_should_match: 1, - }, - }, - + { term: { 'invites.id': inviteId } }, ], }, }, @@ -67,22 +51,56 @@ module.exports = [ inner_hits: {}, }, }, - }) - .then((data) => { + }; + + try { + util.validateFields(fields, ALLOWED_FIELDS); + } catch (validationError) { + const err = new Error(`"fields" is not valid: ${validationError.message}`); + err.status = 400; + return next(err); + } + + if (req.context.inviteType === 'list') { + // user can only his/her own invite with specific id + esSearchParam.query.nested.query.filtered.filter.bool.must.push({ + bool: { + should: [ + { term: { 'invites.email': email } }, + { term: { 'invites.userId': currentUserId } }, + ], + minimum_number_should_match: 1, + }, + }); + } + + return util.fetchByIdFromES('invites', esSearchParam).then((data) => { if (data.length === 0) { req.log.debug('No project member invite found in ES'); - return models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(projectId, email, currentUserId) - .then((invite) => { - if (!invite) { - // check there is an existing invite for the user with status PENDING - // handle 404 - const err = new Error('invite not found for project id ' + - `${projectId}, userId ${currentUserId}, email ${email}`); - err.status = 404; - throw err; + let getInvitePromise; + if (req.context.inviteType === 'all') { + getInvitePromise = models.ProjectMemberInvite.getPendingInviteByIdForUser(projectId, inviteId); + } else { + getInvitePromise = models.ProjectMemberInvite.getPendingInviteByIdForUser( + projectId, inviteId, email, currentUserId); + } + return getInvitePromise.then((invite) => { + if (!invite) { + // check there is an existing invite for the user with status PENDING + // handle 404 + let errMsg; + if (req.context.inviteType === 'all') { + errMsg = `invite not found for project id ${projectId}, inviteId ${inviteId}`; + } else { + errMsg = `invite not found for project id ${projectId}, inviteId ${inviteId}, ` + + `userId ${currentUserId} and email ${email}`; } - return invite; - }); + const err = new Error(errMsg); + err.status = 404; + throw err; + } + return invite; + }); } req.log.debug('project member found in ES'); return data[0].inner_hits.invites.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle @@ -96,7 +114,7 @@ module.exports = [ return invite; }) )) - .then(invite => res.json(util.maskInviteEmails('$[*].email', invite, req))) + .then(invite => res.json(util.postProcessInvites('$.email', invite, req))) .catch(next); }, ]; diff --git a/src/routes/projectMemberInvites/get.spec.js b/src/routes/projectMemberInvites/get.spec.js index 5b04ae5b..4120c1f4 100644 --- a/src/routes/projectMemberInvites/get.spec.js +++ b/src/routes/projectMemberInvites/get.spec.js @@ -9,91 +9,168 @@ import { INVITE_STATUS } from '../../constants'; const should = chai.should(); -describe('GET Project', () => { +describe('GET Project Member Invite', () => { let project1; let project2; before((done) => { - testUtil.clearDb() - .then(() => { - const p1 = models.Project.create({ - type: 'generic', - billingAccountId: 1, - name: 'test1', - description: 'test project1', - status: 'draft', - details: {}, + // clear ES and db + testUtil.clearES().then(() => { + testUtil.clearDb() + .then(() => { + const p1 = models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then((p) => { + project1 = p; + // create members + const pm1 = models.ProjectMember.create({ + userId: testUtil.userIds.admin, + projectId: project1.id, + role: 'copilot', + isPrimary: true, createdBy: 1, updatedBy: 1, - lastActivityAt: 1, - lastActivityUserId: '1', - }).then((p) => { - project1 = p; - // create members - const pm1 = models.ProjectMember.create({ - userId: 40051333, - projectId: project1.id, - role: 'copilot', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - // create invite - const invite1 = models.ProjectMemberInvite.create({ - userId: 40051331, - email: null, - projectId: project1.id, - role: 'customer', - createdBy: 1, - updatedBy: 1, - status: INVITE_STATUS.PENDING, - }); - return Promise.all([pm1, invite1]); }); + // create invite + const invite1 = models.ProjectMemberInvite.create({ + id: 1, + userId: testUtil.userIds.member, + email: null, + projectId: project1.id, + role: 'customer', + createdBy: 1, + updatedBy: 1, + status: INVITE_STATUS.PENDING, + }); + + const invite2 = models.ProjectMemberInvite.create({ + id: 2, + userId: testUtil.userIds.copilot, + email: 'test@topcoder.com', + projectId: project1.id, + role: 'copilot', + createdBy: 1, + updatedBy: 1, + status: INVITE_STATUS.PENDING, + }); + + return Promise.all([pm1, invite1, invite2]); + }); + + const p2 = models.Project.create({ + type: 'visual_design', + billingAccountId: 1, + name: 'test2', + description: 'test project2', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then((p) => { + project2 = p; - const p2 = models.Project.create({ - type: 'visual_design', - billingAccountId: 1, - name: 'test2', - description: 'test project2', - status: 'draft', - details: {}, + // create invite 3 + const invite3 = models.ProjectMemberInvite.create({ + id: 3, + userId: null, + email: 'test@topcoder.com', + projectId: project2.id, + role: 'customer', createdBy: 1, updatedBy: 1, - lastActivityAt: 1, - lastActivityUserId: '1', - }).then((p) => { - project2 = p; + status: INVITE_STATUS.PENDING, }); - return Promise.all([p1, p2]) - .then(() => done()); + + const invite4 = models.ProjectMemberInvite.create({ + id: 4, + userId: testUtil.userIds.member2, + email: null, + projectId: project2.id, + role: 'customer', + createdBy: 1, + updatedBy: 1, + status: INVITE_STATUS.ACCEPTED, + }); + + return Promise.all([invite3, invite4]); }); + return Promise.all([p1, p2]) + .then(() => done()); + }); + }); }); after((done) => { testUtil.clearDb(done); }); - describe('GET /projects/{id}/members/invite', () => { + describe('GET /projects/{projectId}/invites/{inviteId}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get(`/v5/projects/${project2.id}/members/invite`) + .get(`/v5/projects/${project2.id}/invites/1`) .expect(403, done); }); it('should return 404 if requested project doesn\'t exist', (done) => { request(server) - .get('/v5/projects/14343323/members/invite') + .get('/v5/projects/14343323/invites/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(404, done); }); - it('should return the invite if user is invited to this project', (done) => { + it('should return 404 if requested invitation doesn\'t exist', (done) => { request(server) - .get(`/v5/projects/${project1.id}/members/invite`) + .get(`/v5/projects/${project1.id}/invites/12345678`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 if requested invitation and project doesn\'t match', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/invites/3`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 if user can\'t view project and this invitation is not for this user', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/invites/1`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(404, done); + }); + + it('should return 404 if invitation is not in pending or requested status', (done) => { + request(server) + .get(`/v5/projects/${project2.id}/invites/4`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return the invite if user can view the project', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/invites/1`) .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect('Content-Type', /json/) .expect(200) @@ -104,24 +181,57 @@ describe('GET Project', () => { const resJson = res.body; should.exist(resJson); should.exist(resJson.projectId); - resJson.userId.should.be.eql(40051331); + resJson.id.should.be.eql(1); + resJson.userId.should.be.eql(testUtil.userIds.member); resJson.status.should.be.eql(INVITE_STATUS.PENDING); done(); } }); }); - it('should return 404 if user is not invited to this project', (done) => { + it('should return the invite if this invitation is for logged-in user', (done) => { request(server) - .get(`/v5/projects/${project2.id}/members/invite`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .expect('Content-Type', /json/) - .expect(404) - .end(() => { + .get(`/v5/projects/${project1.id}/invites/2`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + should.exist(resJson.projectId); + resJson.id.should.be.eql(2); + resJson.status.should.be.eql(INVITE_STATUS.PENDING); done(); - }); + } + }); + }); + + it('should return the invite if user get invitation by email', (done) => { + request(server) + .get(`/v5/projects/${project2.id}/invites/3`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + should.exist(resJson.projectId); + resJson.id.should.be.eql(3); + resJson.email.should.be.eql('t***t@t***r.com'); // masked + resJson.status.should.be.eql(INVITE_STATUS.PENDING); + done(); + } + }); }); }); }); diff --git a/src/routes/projectMemberInvites/list.js b/src/routes/projectMemberInvites/list.js index b5ef00f0..7c531930 100644 --- a/src/routes/projectMemberInvites/list.js +++ b/src/routes/projectMemberInvites/list.js @@ -26,17 +26,11 @@ module.exports = [ permissions('projectMemberInvite.list'), (req, res, next) => { const projectId = _.parseInt(req.params.projectId); + const currentUserId = req.authUser.userId; + const email = req.authUser.email; const fields = req.query.fields ? req.query.fields.split(',') : null; - try { - util.validateFields(fields, ALLOWED_FIELDS); - } catch (validationError) { - const err = new Error(`"fields" is not valid: ${validationError.message}`); - err.status = 400; - return next(err); - } - - return util.fetchByIdFromES('invites', { + const esSearchParam = { query: { nested: { path: 'invites', @@ -62,24 +56,54 @@ module.exports = [ }, }, }, - }) - .then((data) => { - if (data.length === 0) { - req.log.debug('No project member invites found in ES'); - return models.ProjectMemberInvite.getPendingAndReguestedInvitesForProject(projectId); - } - req.log.debug('project member found in ES'); - return data[0].inner_hits.invites.hits.hits.map(hit => hit._source); // eslint-disable-line no-underscore-dangle - }).then(invites => ( - util.getObjectsWithMemberDetails(invites, fields, req) - .catch((err) => { - req.log.error('Cannot get user details for invites.'); - req.log.debug('Error during getting user details for invites.', err); - // continues without details anyway - return invites; - }) - )) - .then(invites => res.json(util.maskInviteEmails('$[*].email', invites, req))) - .catch(next); + }; + + if (req.context.inviteType === 'list') { + // user has no "view" project permission + // try to search from es, add search by user id or email + esSearchParam.query.nested.query.filtered.filter.bool.must.push({ + bool: { + should: [ + { term: { 'invites.email': email } }, + { term: { 'invites.userId': currentUserId } }, + ], + minimum_number_should_match: 1, + }, + }); + } + + try { + util.validateFields(fields, ALLOWED_FIELDS); + } catch (validationError) { + const err = new Error(`"fields" is not valid: ${validationError.message}`); + err.status = 400; + return next(err); + } + + return util.fetchByIdFromES('invites', esSearchParam) + .then((data) => { + if (data.length === 0) { + req.log.debug('No project member invites found in ES'); + // if user has "view" project permission, get all invites + if (req.context.inviteType === 'all') { + return models.ProjectMemberInvite.getPendingOrRequestedProjectInvitesForUser(projectId); + } + // get invitation only for user + return models.ProjectMemberInvite.getPendingOrRequestedProjectInvitesForUser( + projectId, email, currentUserId); + } + req.log.debug('project member found in ES'); + return data[0].inner_hits.invites.hits.hits.map(hit => hit._source); // eslint-disable-line no-underscore-dangle + }).then(invites => ( + util.getObjectsWithMemberDetails(invites, fields, req) + .catch((err) => { + req.log.error('Cannot get user details for invites.'); + req.log.debug('Error during getting user details for invites.', err); + // continues without details anyway + return invites; + }) + )) + .then(invites => res.json(util.postProcessInvites('$[*]', invites, req))) + .catch(next); }, ]; diff --git a/src/routes/projectMemberInvites/list.spec.js b/src/routes/projectMemberInvites/list.spec.js new file mode 100644 index 00000000..56388f2e --- /dev/null +++ b/src/routes/projectMemberInvites/list.spec.js @@ -0,0 +1,261 @@ +/* eslint-disable no-unused-expressions */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; +import { INVITE_STATUS } from '../../constants'; + +const should = chai.should(); + +describe('GET Project Member Invites', () => { + let project1; + let project2; + before((done) => { + // clear ES and db + testUtil.clearES().then(() => { + testUtil.clearDb() + .then(() => { + const p1 = models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then((p) => { + project1 = p; + // create members + const pm1 = models.ProjectMember.create({ + userId: testUtil.userIds.admin, + projectId: project1.id, + role: 'copilot', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); + // create invite + const invite1 = models.ProjectMemberInvite.create({ + id: 1, + userId: testUtil.userIds.member, + email: null, + projectId: project1.id, + role: 'customer', + createdBy: 1, + updatedBy: 1, + status: INVITE_STATUS.PENDING, + }); + + const invite2 = models.ProjectMemberInvite.create({ + id: 2, + userId: testUtil.userIds.copilot, + email: null, + projectId: project1.id, + role: 'copilot', + createdBy: 1, + updatedBy: 1, + status: INVITE_STATUS.PENDING, + }); + + return Promise.all([pm1, invite1, invite2]); + }); + + const p2 = models.Project.create({ + type: 'visual_design', + billingAccountId: 1, + name: 'test2', + description: 'test project2', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then((p) => { + project2 = p; + + // create invite 3 + const invite3 = models.ProjectMemberInvite.create({ + id: 3, + userId: null, + email: 'test@topcoder.com', + projectId: project2.id, + role: 'customer', + createdBy: 1, + updatedBy: 1, + status: INVITE_STATUS.PENDING, + }); + + const invite4 = models.ProjectMemberInvite.create({ + id: 4, + userId: testUtil.userIds.member2, + email: null, + projectId: project2.id, + role: 'customer', + createdBy: 1, + updatedBy: 1, + status: INVITE_STATUS.ACCEPTED, + }); + + return Promise.all([invite3, invite4]); + }); + return Promise.all([p1, p2]) + .then(() => done()); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/invites', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get(`/v5/projects/${project2.id}/invites`) + .expect(403, done); + }); + + it('should return empty result if requested project doesn\'t exist', (done) => { + request(server) + .get('/v5/projects/14343323/invites') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.be.an('array'); + resJson.length.should.be.eql(0); + done(); + } + }); + }); + + it('should return all invitation if user can view the project', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/invites`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.be.an('array'); + resJson.length.should.be.eql(2); + // check invitations + _.filter(resJson, inv => inv.id === 1).length.should.be.eql(1); + _.filter(resJson, inv => inv.id === 2).length.should.be.eql(1); + done(); + } + }); + }); + + it('should return only pending/requested invitation if user can view the project', (done) => { + request(server) + .get(`/v5/projects/${project2.id}/invites`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.be.an('array'); + resJson.length.should.be.eql(1); + // check invitations + _.filter(resJson, inv => inv.id === 3).length.should.be.eql(1); + done(); + } + }); + }); + + it('should return only his/her invitation for logged-in user', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/invites`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.be.an('array'); + resJson.length.should.be.eql(1); + // check invitations + _.filter(resJson, inv => inv.id === 2).length.should.be.eql(1); + should.not.exist(resJson[0].email); + done(); + } + }); + }); + + it('should return empty result for logged-in user has no invitation', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/invites`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.be.an('array'); + resJson.length.should.be.eql(0); + done(); + } + }); + }); + + it('should return the invite if user get invitation by email', (done) => { + request(server) + .get(`/v5/projects/${project2.id}/invites`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.be.an('array'); + resJson.length.should.be.eql(1); + // check invitations + _.filter(resJson, inv => inv.id === 3).length.should.be.eql(1); + resJson[0].email.should.be.eql('t***t@t***r.com'); // masked + done(); + } + }); + }); + }); +}); diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index 5b779e82..883934eb 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -4,7 +4,8 @@ import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, INVITE_STATUS, EVENT, RESOURCES, USER_ROLE } from '../../constants'; +import { INVITE_STATUS, EVENT, RESOURCES } from '../../constants'; +import { PERMISSION } from '../../permissions/constants'; /** * API to update invite member to project. @@ -15,10 +16,6 @@ const permissions = tcMiddleware.permissions; const updateMemberValidations = { body: Joi.object() .keys({ - userId: Joi.number().optional(), - email: Joi.string() - .email() - .optional(), status: Joi.any() .valid(_.values(INVITE_STATUS)) .required(), @@ -29,115 +26,110 @@ const updateMemberValidations = { module.exports = [ // handles request validations validate(updateMemberValidations), - permissions('projectMemberInvite.put'), + permissions('projectMemberInvite.edit'), (req, res, next) => { - const putInvite = req.body; - const projectId = _.parseInt(req.params.projectId); - - // userId or email should be provided - if (!putInvite.userId && !putInvite.email) { - const err = new Error('userId or email should be provided'); + const newStatus = req.body.status; + if (newStatus === INVITE_STATUS.CANCELED) { + const err = new Error('Cannot change invite status to “canceled”. Please, delete the invite instead.'); err.status = 400; return next(err); } + const projectId = _.parseInt(req.params.projectId); + const inviteId = _.parseInt(req.params.inviteId); + const email = req.authUser.email; + const currentUserId = req.authUser.userId; - let invite; - let requestedInvite; - return models.ProjectMemberInvite.getPendingInviteByEmailOrUserId( - projectId, - putInvite.email, - putInvite.userId, - ).then((_invite) => { - invite = _invite; - }).then(() => models.ProjectMemberInvite.getRequestedInvite(projectId, putInvite.userId)) - .then((_requestedInvite) => { - requestedInvite = _requestedInvite; - if (!invite && !requestedInvite) { - // check there is an existing invite for the user with status PENDING - // handle 404 - const err = new Error( - `invite not found for project id ${projectId}, email ${putInvite.email} and userId ${putInvite.userId}`, - ); - err.status = 404; - return next(err); - } + // get invite by id and project id + return models.ProjectMemberInvite.getPendingOrRequestedProjectInviteById(projectId, inviteId) + .then((invite) => { + // if invite doesn't exist, return 404 + if (!invite) { + const err = new Error(`invite not found for project id ${projectId}, inviteId ${inviteId},` + + ` email ${email} and userId ${currentUserId}`, + ); + err.status = 404; + return next(err); + } + // check this invitation is for logged-in user or not + const ownInvite = (!!invite && (invite.userId === currentUserId || invite.email === email)); - invite = invite || requestedInvite; + // check permission + req.log.debug('Checking user permission for updating invite'); + let error = null; - req.log.debug('Chekcing user permission for updating invite'); - let error = null; - if (invite.status === INVITE_STATUS.REQUESTED && - !util.hasRoles(req, [USER_ROLE.CONNECT_ADMIN, USER_ROLE.COPILOT_MANAGER])) { - error = 'Requested invites can only be updated by Copilot manager'; - } else if (putInvite.status === INVITE_STATUS.CANCELED) { - if (!util.hasRoles(req, MANAGER_ROLES) && invite.role !== PROJECT_MEMBER_ROLE.CUSTOMER) { - error = `Project members can cancel invites only for ${PROJECT_MEMBER_ROLE.CUSTOMER}`; + if ( + invite.status === INVITE_STATUS.REQUESTED + && !util.hasPermission(PERMISSION.UPDATE_REQUESTED_INVITE, req.authUser, req.context.currentProjectMembers) + ) { + error = 'You don\'t have permissions to update requested invites.'; + } else if ( + invite.status !== INVITE_STATUS.REQUESTED + && !ownInvite + && !util.hasPermission(PERMISSION.UPDATE_NOT_OWN_INVITE, req.authUser, req.context.currentProjectMembers) + ) { + error = 'You don\'t have permissions to update invites for other users.'; } - } else if (((!!putInvite.userId && putInvite.userId !== req.authUser.userId) || - (!!putInvite.email && putInvite.email !== req.authUser.email)) && - !util.hasRoles(req, [USER_ROLE.CONNECT_ADMIN, USER_ROLE.COPILOT_MANAGER])) { - error = 'Project members can only update invites for themselves'; - } - if (error) { - const err = new Error(error); - err.status = 403; - return next(err); - } + if (error) { + const err = new Error(error); + err.status = 403; + return next(err); + } - req.log.debug('Updating invite status'); - return invite - .update({ - status: putInvite.status, - }) - .then((updatedInvite) => { - // emit the event - util.sendResourceToKafkaBus( - req, - EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, - RESOURCES.PROJECT_MEMBER_INVITE, - updatedInvite.toJSON()); + req.log.debug('Updating invite status'); + return invite + .update({ + status: newStatus, + }) + .then((updatedInvite) => { + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, + RESOURCES.PROJECT_MEMBER_INVITE, + updatedInvite.toJSON()); - req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, updatedInvite, { - correlationId: req.id, - }); + req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, updatedInvite, { + correlationId: req.id, + }); - req.log.debug('Adding user to project'); - // add user to project if accept invite - if (updatedInvite.status === INVITE_STATUS.ACCEPTED || + req.log.debug('Adding user to project'); + // add user to project if accept invite + if (updatedInvite.status === INVITE_STATUS.ACCEPTED || updatedInvite.status === INVITE_STATUS.REQUEST_APPROVED) { - return models.ProjectMember.getActiveProjectMembers(projectId) - .then((members) => { - req.context = req.context || {}; - req.context.currentProjectMembers = members; - let userId = updatedInvite.userId; - // if the requesting user is updating his/her own invite - if (!userId && req.authUser.email === updatedInvite.email) { - userId = req.authUser.userId; - } - // if we are not able to identify the user yet, it must be something wrong and we should not create - // project member - if (!userId) { - const err = new Error( - `Unable to find userId for the invite. ${updatedInvite.email} has not joined topcoder yet.`); - err.status = 400; - return next(err); - } - const member = { - projectId, - role: updatedInvite.role, - userId, - createdBy: req.authUser.userId, - updatedBy: req.authUser.userId, - }; - return util - .addUserToProject(req, member) - .then(() => res.json(util.maskInviteEmails('$.email', updatedInvite, req))) - .catch(err => next(err)); - }); - } - return res.json(util.maskInviteEmails('$.email', updatedInvite, req)); - }); - }); + return models.ProjectMember.getActiveProjectMembers(projectId) + .then((members) => { + req.context = req.context || {}; + req.context.currentProjectMembers = members; + let userId = updatedInvite.userId; + // if the requesting user is updating his/her own invite + if (!userId && email === updatedInvite.email) { + userId = currentUserId; + } + // if we are not able to identify the user yet, it must be something wrong and we should not create + // project member + if (!userId) { + const err = new Error( + `Unable to find userId for the invite. ${updatedInvite.email} has not joined topcoder yet.`); + err.status = 400; + return next(err); + } + const member = { + projectId, + role: updatedInvite.role, + userId, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }; + return util + .addUserToProject(req, member) + .then(() => res.json(util.postProcessInvites('$.email', updatedInvite, req))) + .catch(err => next(err)); + }); + } + return res.json(util.postProcessInvites('$.email', updatedInvite, req)); + }); + }) + .catch(next); }, ]; diff --git a/src/routes/projectMemberInvites/update.spec.js b/src/routes/projectMemberInvites/update.spec.js index 49e53e3c..bd1b6c3c 100644 --- a/src/routes/projectMemberInvites/update.spec.js +++ b/src/routes/projectMemberInvites/update.spec.js @@ -11,7 +11,6 @@ import busApi from '../../services/busApi'; import { BUS_API_EVENT, RESOURCES, - USER_ROLE, PROJECT_MEMBER_ROLE, INVITE_STATUS, CONNECT_NOTIFICATION_EVENT, @@ -21,14 +20,12 @@ const should = chai.should(); describe('Project member invite update', () => { let project1; - let invite1; - let invite2; - let invite3; + let project2; beforeEach((done) => { testUtil.clearDb() .then(() => { - models.Project.create({ + const p1 = models.Project.create({ type: 'generic', directProjectId: 1, billingAccountId: 1, @@ -43,8 +40,8 @@ describe('Project member invite update', () => { }).then((p) => { project1 = p; // create members - models.ProjectMember.create({ - userId: 40051334, + const pm1 = models.ProjectMember.create({ + userId: testUtil.userIds.manager, projectId: project1.id, role: 'manager', isPrimary: false, @@ -52,55 +49,119 @@ describe('Project member invite update', () => { updatedBy: 1, createdAt: '2016-06-30 00:33:07+00', updatedAt: '2016-06-30 00:33:07+00', - }).then(() => { - models.ProjectMemberInvite.create({ - projectId: project1.id, - userId: 40051331, - email: null, - role: PROJECT_MEMBER_ROLE.CUSTOMER, - status: INVITE_STATUS.PENDING, - createdBy: 1, - updatedBy: 1, - createdAt: '2016-06-30 00:33:07+00', - updatedAt: '2016-06-30 00:33:07+00', - }).then((in1) => { - invite1 = in1.get({ - plain: true, - }); - models.ProjectMemberInvite.create({ - projectId: project1.id, - userId: 40051334, - email: null, - role: PROJECT_MEMBER_ROLE.MANAGER, - status: INVITE_STATUS.PENDING, - createdBy: 1, - updatedBy: 1, - createdAt: '2016-06-30 00:33:07+00', - updatedAt: '2016-06-30 00:33:07+00', - }).then((in2) => { - invite2 = in2.get({ - plain: true, - }); - models.ProjectMemberInvite.create({ - projectId: project1.id, - userId: 40051332, - email: null, - role: PROJECT_MEMBER_ROLE.COPILOT, - status: INVITE_STATUS.REQUESTED, - createdBy: 1, - updatedBy: 1, - createdAt: '2016-06-30 00:33:07+00', - updatedAt: '2016-06-30 00:33:07+00', - }).then((in3) => { - invite3 = in3.get({ - plain: true, - }); - done(); - }); - }); - }); }); + + const invite1 = models.ProjectMemberInvite.create({ + id: 1, + projectId: project1.id, + userId: testUtil.userIds.member, + email: null, + role: PROJECT_MEMBER_ROLE.CUSTOMER, + status: INVITE_STATUS.PENDING, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite2 = models.ProjectMemberInvite.create({ + id: 2, + projectId: project1.id, + userId: testUtil.userIds.copilot, + email: null, + role: PROJECT_MEMBER_ROLE.COPILOT, + status: INVITE_STATUS.REQUESTED, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite3 = models.ProjectMemberInvite.create({ + id: 3, + projectId: project1.id, + userId: testUtil.userIds.manager, + email: null, + role: PROJECT_MEMBER_ROLE.MANAGER, + status: INVITE_STATUS.PENDING, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + return Promise.all([pm1, invite1, invite2, invite3]); }); + + const p2 = models.Project.create({ + type: 'generic', + directProjectId: 1, + billingAccountId: 1, + name: 'test2', + description: 'test project2', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then((p) => { + project2 = p; + // create members + const pm = models.ProjectMember.create({ + userId: testUtil.userIds.manager, + projectId: project2.id, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite4 = models.ProjectMemberInvite.create({ + id: 4, + projectId: project2.id, + userId: testUtil.userIds.member, + email: null, + role: PROJECT_MEMBER_ROLE.CUSTOMER, + status: INVITE_STATUS.PENDING, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite5 = models.ProjectMemberInvite.create({ + id: 5, + projectId: project2.id, + userId: null, + email: 'romit.choudhary@rivigo.com', + role: PROJECT_MEMBER_ROLE.CUSTOMER, + status: INVITE_STATUS.PENDING, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + const invite6 = models.ProjectMemberInvite.create({ + id: 6, + projectId: project2.id, + userId: testUtil.userIds.copilot, + email: null, + role: PROJECT_MEMBER_ROLE.COPILOT, + status: INVITE_STATUS.ACCEPTED, + createdBy: 1, + updatedBy: 1, + createdAt: '2016-06-30 00:33:07+00', + updatedAt: '2016-06-30 00:33:07+00', + }); + + return Promise.all([pm, invite4, invite5, invite6]); + }); + + Promise.all([p1, p2]).then(() => done()); }); }); @@ -108,7 +169,7 @@ describe('Project member invite update', () => { testUtil.clearDb(done); }); - describe('PUT /projects/{id}/members/invite', () => { + describe('PUT /projects/{id}/invites', () => { const body = { status: 'accepted', }; @@ -123,20 +184,19 @@ describe('Project member invite update', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .patch(`/v5/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/invites/1`) .send(body) .expect(403, done); }); - it('should return 404 if user has no invite', (done) => { + it('should return 404 if invitation id and project id doesn\'t match', (done) => { request(server) - .put(`/v5/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/invites/5`) .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - userId: 123, - status: INVITE_STATUS.CANCELED, + status: INVITE_STATUS.ACCEPTED, }) .expect('Content-Type', /json/) .expect(404) @@ -145,98 +205,62 @@ describe('Project member invite update', () => { }); }); - it('should return 400 no userId or email is presented', (done) => { + it('should return 404 if project id doesn\'t exist', (done) => { request(server) - .put(`/v5/projects/${project1.id}/members/invite`) + .patch('/v5/projects/99999/invites/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - status: INVITE_STATUS.CANCELED, + status: INVITE_STATUS.ACCEPTED, }) .expect('Content-Type', /json/) - .expect(400) - .end((err, res) => { - if (err) { - done(err); - } else { - const resJson = res.body; - should.exist(resJson); - const errorMessage = _.get(resJson, 'message', ''); - sinon.assert.match(errorMessage, /.*userId or email should be provided/); - done(); - } + .expect(404) + .end(() => { + done(); }); }); - it('should return 403 if try to update MANAGER role invite with copilot', (done) => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - get: () => Promise.resolve({ - status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: [{ - roleName: USER_ROLE.COPILOT, - }], - }, - }, - }), - }); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + it('should return 404 if invitation id doesn\'t exist', (done) => { request(server) - .put(`/v5/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/invites/99999`) .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - userId: invite2.userId, - status: INVITE_STATUS.CANCELED, + status: INVITE_STATUS.ACCEPTED, }) .expect('Content-Type', /json/) - .expect(403) - .end((err, res) => { - if (err) { - done(err); - } else { - const resJson = res.body; - should.exist(resJson); - const errorMessage = _.get(resJson, 'message', ''); - sinon.assert.match(errorMessage, /.*Project members can cancel invites only for customer/); - done(); - } + .expect(404) + .end(() => { + done(); + }); + }); + + it('should return 404 if invitation status is not pending or requested', (done) => { + request(server) + .patch(`/v5/projects/${project2.id}/invites/6`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + status: INVITE_STATUS.ACCEPTED, + }) + .expect('Content-Type', /json/) + .expect(404) + .end(() => { + done(); }); }); it('should return 403 if try to update others invite with CUSTOMER', (done) => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - get: () => Promise.resolve({ - status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: [{ - roleName: USER_ROLE.CUSTOMER, - }], - }, - }, - }), - }); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .put(`/v5/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/invites/1`) .set({ - Authorization: `Bearer ${testUtil.jwts.member2}`, + Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - userId: invite2.userId, - status: INVITE_STATUS.CANCELED, + status: INVITE_STATUS.ACCEPTED, }) .expect('Content-Type', /json/) .expect(403) @@ -247,37 +271,22 @@ describe('Project member invite update', () => { const resJson = res.body; should.exist(resJson); const errorMessage = _.get(resJson, 'message', ''); - sinon.assert.match(errorMessage, /.*Project members can cancel invites only for customer/); + sinon.assert.match( + errorMessage, + 'You don\'t have permissions to update invites for other users.', + ); done(); } }); }); it('should return 403 if try to update COPILOT role invite with copilot', (done) => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - get: () => Promise.resolve({ - status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: [{ - roleName: USER_ROLE.COPILOT, - }], - }, - }, - }), - }); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .put(`/v5/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/invites/2`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - userId: invite3.userId, status: INVITE_STATUS.ACCEPTED, }) .expect('Content-Type', /json/) @@ -289,12 +298,67 @@ describe('Project member invite update', () => { const resJson = res.body; should.exist(resJson); const errorMessage = _.get(resJson, 'message', ''); - sinon.assert.match(errorMessage, 'Requested invites can only be updated by Copilot manager'); + sinon.assert.match(errorMessage, 'You don\'t have permissions to update requested invites.'); done(); } }); }); + it('should return 200 if member accepts his/her invitation', (done) => { + request(server) + .patch(`/v5/projects/${project1.id}/invites/1`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + status: INVITE_STATUS.ACCEPTED, + }) + .expect('Content-Type', /json/) + .expect(200) + .end(() => done()); + }); + + it('should return 200 if admin accepts his/her invitation', (done) => { + request(server) + .patch(`/v5/projects/${project1.id}/invites/1`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + status: INVITE_STATUS.ACCEPTED, + }) + .expect('Content-Type', /json/) + .expect(200) + .end(() => done()); + }); + + it('should return 200 if copilot accepts his/her invitation', (done) => { + request(server) + .patch(`/v5/projects/${project1.id}/invites/2`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + status: INVITE_STATUS.ACCEPTED, + }) + .expect('Content-Type', /json/) + .expect(200) + .end(() => done()); + }); + + it('should return 200 if user accept invitation by email', (done) => { + request(server) + .patch(`/v5/projects/${project1.id}/invites/5`) + .set({ + Authorization: `Bearer ${testUtil.jwts.romit}`, + }) + .send({ + status: INVITE_STATUS.ACCEPTED, + }) + .expect('Content-Type', /json/) + .expect(200) + .end(() => done()); + }); describe('Bus api', () => { let createEventSpy; @@ -317,12 +381,11 @@ describe('Project member invite update', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .put(`/v5/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/invites/1`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send({ - userId: invite1.userId, status: INVITE_STATUS.ACCEPTED, }) .expect('Content-Type', /json/) @@ -334,13 +397,11 @@ describe('Project member invite update', () => { testUtil.wait(() => { createEventSpy.callCount.should.be.eql(5); - /* - Events for accepted invite - */ + // Events for accepted invite createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE, projectId: project1.id, - userId: invite1.userId, + userId: testUtil.userIds.member, status: INVITE_STATUS.ACCEPTED, email: null, })).should.be.true; @@ -348,33 +409,31 @@ describe('Project member invite update', () => { // Check Notification Service events createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({ projectId: project1.id, - userId: invite1.userId, + userId: testUtil.userIds.member, status: INVITE_STATUS.ACCEPTED, email: null, isSSO: false, })).should.be.true; - /* - Events for created member (after invite acceptance) - */ + // Events for created member (after invite acceptance) createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_ADDED, sinon.match({ resource: RESOURCES.PROJECT_MEMBER, projectId: project1.id, - userId: invite1.userId, + userId: testUtil.userIds.member, })).should.be.true; // Check Notification Service events createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED, sinon.match({ projectId: project1.id, projectName: project1.name, - userId: invite1.userId, - initiatorUserId: 40051331, + userId: testUtil.userIds.member, + initiatorUserId: testUtil.userIds.member, })).should.be.true; createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ projectId: project1.id, projectName: project1.name, - userId: invite1.userId, - initiatorUserId: 40051331, + userId: testUtil.userIds.member, + initiatorUserId: testUtil.userIds.member, })).should.be.true; done(); diff --git a/src/routes/projectMembers/create.spec.js b/src/routes/projectMembers/create.spec.js index 75a0c234..152a476b 100644 --- a/src/routes/projectMembers/create.spec.js +++ b/src/routes/projectMembers/create.spec.js @@ -69,29 +69,46 @@ describe('Project Members create', () => { it('should return 201 when invited then accepted and then 404 if user is already as a member', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { - get: () => Promise.resolve({ - status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: [{ - roleName: USER_ROLE.COPILOT, - }], + get: (url) => { + const testCopilot = { + userId: 40051332, + handle: 'test_copilot1', + firstName: 'Firstname', + lastName: 'Lastname', + email: 'test_copilot1@email.com', + }; + const testRoleName = { + roleName: USER_ROLE.COPILOT_MANAGER, + }; + const ret = { + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: [], + }, }, - }, - }), + }; + + if (url.indexOf('/_search') >= 0) { + ret.data.result.content.push(testCopilot); + } else { + ret.data.result.content.push(testRoleName); + } + return Promise.resolve(ret); + }, }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - userIds: [40051332], + handles: ['test_copilot1'], role: 'copilot', }) .expect('Content-Type', /json/) @@ -105,14 +122,14 @@ describe('Project Members create', () => { resJson.role.should.equal('copilot'); resJson.projectId.should.equal(project1.id); resJson.userId.should.equal(40051332); + should.exist(resJson.id); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; request(server) - .put(`/v5/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/invites/${resJson.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send({ - userId: 40051332, status: 'accepted', }) .expect('Content-Type', /json/) @@ -130,12 +147,11 @@ describe('Project Members create', () => { server.services.pubsub.publish.calledWith('project.member.added').should.be.true; request(server) - .put(`/v5/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/invites/${resJson.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send({ - userId: 40051332, status: 'accepted', }) .expect('Content-Type', /json/) @@ -145,7 +161,7 @@ describe('Project Members create', () => { done(err3); } else { const errorMessage = _.get(res3.body, 'message', ''); - sinon.assert.match(errorMessage, /.*invite not found for project id 1, email undefined and userId/); + sinon.assert.match(errorMessage, /.*invite not found for project id 1, inviteId/); done(); } }); @@ -349,27 +365,63 @@ describe('Project Members create', () => { }); it('should send correct BUS API messages when copilot added', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + get: (url) => { + const testCopilot = { + userId: 40051332, + handle: 'test_copilot1', + firstName: 'Firstname', + lastName: 'Lastname', + email: 'test_copilot1@email.com', + }; + const testRoleName = { + roleName: USER_ROLE.COPILOT_MANAGER, + }; + const ret = { + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: [], + }, + }, + }; + + if (url.indexOf('/_search') >= 0) { + ret.data.result.content.push(testCopilot); + } else { + ret.data.result.content.push(testRoleName); + } + return Promise.resolve(ret); + }, + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v5/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/invites`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - userIds: [40051332], + handles: ['test_copilot1'], role: 'copilot', }) .expect(201) - .end((err) => { + .end((err, inviteRes) => { if (err) { done(err); } else { + const inviteResJson = inviteRes.body.success[0]; + should.exist(inviteResJson); + should.exist(inviteResJson.id); request(server) - .put(`/v5/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/invites/${inviteResJson.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send({ - userId: 40051332, status: 'accepted', }) .expect('Content-Type', /json/) diff --git a/src/routes/projectMembers/get.js b/src/routes/projectMembers/get.js index de40a38d..e30b7054 100644 --- a/src/routes/projectMembers/get.js +++ b/src/routes/projectMembers/get.js @@ -7,6 +7,8 @@ import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; +const PROJECT_MEMBER_ATTRIBUTES = _.without(_.keys(models.ProjectMember.rawAttributes)); + /** * API to get project member. * @@ -30,7 +32,7 @@ module.exports = [ (req, res, next) => { const projectId = _.parseInt(req.params.projectId); const memberRecordId = _.parseInt(req.params.id); - const fields = req.query.fields ? req.query.fields.split(',') : null; + const fields = req.query.fields ? req.query.fields.split(',') : []; util.fetchByIdFromES('members', { query: { @@ -74,7 +76,15 @@ module.exports = [ }); } req.log.debug('project member found in ES'); - return data[0].inner_hits.members.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle + return _.pick( + data[0].inner_hits.members.hits.hits[0]._source, // eslint-disable-line no-underscore-dangle + // Elasticsearch index might have additional fields added to members like + // 'handle', 'firstName', 'lastName', 'email' + // but we shouldn't return them, as they might be outdated + // method "getObjectsWithMemberDetails" would populate these fields again + // with up to date data from Member Service if necessary + PROJECT_MEMBER_ATTRIBUTES, + ); }).then(member => ( util.getObjectsWithMemberDetails([member], fields, req) .then(([memberWithDetails]) => memberWithDetails) diff --git a/src/routes/projectMembers/list.js b/src/routes/projectMembers/list.js index a1cad685..cd694a64 100644 --- a/src/routes/projectMembers/list.js +++ b/src/routes/projectMembers/list.js @@ -9,6 +9,8 @@ import models from '../../models'; import util from '../../util'; import { PROJECT_MEMBER_ROLE } from '../../constants'; +const PROJECT_MEMBER_ATTRIBUTES = _.without(_.keys(models.ProjectMember.rawAttributes)); + const permissions = tcMiddleware.permissions; const schema = { @@ -31,7 +33,7 @@ module.exports = [ permissions('project.viewMember'), (req, res, next) => { const projectId = _.parseInt(req.params.projectId); - const fields = req.query.fields ? req.query.fields.split(',') : null; + const fields = req.query.fields ? req.query.fields.split(',') : []; const must = [ { term: { 'members.projectId': projectId } }, ]; @@ -89,7 +91,15 @@ module.exports = [ }); } req.log.debug('project members found in ES'); - return data[0].inner_hits.members.hits.hits.map(hit => hit._source); // eslint-disable-line no-underscore-dangle + return data[0].inner_hits.members.hits.hits.map(hit => _.pick( + hit._source, // eslint-disable-line no-underscore-dangle + // Elasticsearch index might have additional fields added to members like + // 'handle', 'firstName', 'lastName', 'email' + // but we shouldn't return them, as they might be outdated + // method "getObjectsWithMemberDetails" would populate these fields again + // with up to date data from Member Service if necessary + PROJECT_MEMBER_ATTRIBUTES, + )); }) .then(members => ( util.getObjectsWithMemberDetails(members, fields, req) diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js index b0bb815f..567be713 100644 --- a/src/routes/projects/create.js +++ b/src/routes/projects/create.js @@ -8,7 +8,7 @@ import moment from 'moment'; import models from '../../models'; import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, PROJECT_STATUS, PROJECT_PHASE_STATUS, - EVENT, RESOURCES, REGEX, WORKSTREAM_STATUS } from '../../constants'; + EVENT, RESOURCES, REGEX, WORKSTREAM_STATUS, ATTACHMENT_TYPES } from '../../constants'; import fieldLookupValidation from '../../middlewares/fieldLookupValidation'; import util from '../../util'; @@ -70,9 +70,11 @@ const createProjectValidations = { })).optional(), attachments: Joi.array().items(Joi.object().keys({ category: Joi.string().required(), - contentType: Joi.string().required(), + contentType: Joi.string().when('type', { is: ATTACHMENT_TYPES.FILE, then: Joi.string().required() }), description: Joi.string().allow(null).allow('').optional(), - filePath: Joi.string().required(), + path: Joi.string().required(), + type: Joi.string().required(), + tags: Joi.array().items(Joi.string().min(1)).optional(), size: Joi.number().required(), title: Joi.string().required(), })).optional(), diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index 35abffea..9f83df71 100644 --- a/src/routes/projects/create.spec.js +++ b/src/routes/projects/create.spec.js @@ -10,6 +10,7 @@ import server from '../../app'; import testUtil from '../../tests/util'; import RabbitMQService from '../../services/rabbitmq'; import models from '../../models'; +import { ATTACHMENT_TYPES } from '../../constants'; const should = chai.should(); const expect = chai.expect; @@ -271,6 +272,35 @@ describe('Project create', () => { }], }; + const bodyWithAttachments = { + type: 'generic', + description: 'test project', + details: {}, + billingAccountId: 1, + name: 'test project1', + attachments: [ + { + title: 'file1.txt', + description: 'blah', + contentType: 'application/unknown', + size: 12312, + category: 'categ1', + path: 'https://media.topcoder.com/projects/1/test.txt', + type: ATTACHMENT_TYPES.FILE, + tags: ['tag1', 'tag2'], + }, + { + title: 'Test Link 1', + description: 'Test link 1 description', + size: 123456, + category: 'categ1', + path: 'https://connect.topcoder-dev.com/projects/8600/assets', + type: ATTACHMENT_TYPES.LINK, + tags: ['tag3', 'tag4'], + }, + ], + }; + let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); @@ -478,6 +508,88 @@ describe('Project create', () => { }); }); + it('should return 201 if valid user and data with attachments', (done) => { + const validBody = _.cloneDeep(bodyWithAttachments); + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: { + projectId: 128, + }, + }, + }, + }), + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .post('/v5/projects') + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(validBody) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + should.exist(resJson.billingAccountId); + should.exist(resJson.name); + resJson.status.should.be.eql('in_review'); + resJson.type.should.be.eql(bodyWithAttachments.type); + resJson.version.should.be.eql('v2'); + resJson.members.should.have.lengthOf(1); + resJson.members[0].role.should.be.eql('customer'); + resJson.members[0].userId.should.be.eql(40051331); + resJson.members[0].projectId.should.be.eql(resJson.id); + resJson.members[0].isPrimary.should.be.truthy; + + resJson.attachments.should.have.lengthOf(2); + + should.exist(resJson.attachments[0].id); + should.exist(resJson.attachments[0].createdAt); + should.exist(resJson.attachments[0].updatedAt); + resJson.attachments[0].createdBy.should.equal(40051331); + resJson.attachments[0].updatedBy.should.equal(40051331); + resJson.attachments[0].title.should.equal(bodyWithAttachments.attachments[0].title); + resJson.attachments[0].description.should.equal(bodyWithAttachments.attachments[0].description); + resJson.attachments[0].contentType.should.equal(bodyWithAttachments.attachments[0].contentType); + resJson.attachments[0].size.should.equal(bodyWithAttachments.attachments[0].size); + resJson.attachments[0].category.should.equal(bodyWithAttachments.attachments[0].category); + resJson.attachments[0].path.should.equal(bodyWithAttachments.attachments[0].path); + resJson.attachments[0].type.should.equal(bodyWithAttachments.attachments[0].type); + resJson.attachments[0].tags.should.eql(bodyWithAttachments.attachments[0].tags); + + should.exist(resJson.attachments[1].id); + should.exist(resJson.attachments[1].createdAt); + should.exist(resJson.attachments[1].updatedAt); + resJson.attachments[1].createdBy.should.equal(40051331); + resJson.attachments[1].updatedBy.should.equal(40051331); + resJson.attachments[1].title.should.equal(bodyWithAttachments.attachments[1].title); + resJson.attachments[1].description.should.equal(bodyWithAttachments.attachments[1].description); + resJson.attachments[1].size.should.equal(bodyWithAttachments.attachments[1].size); + resJson.attachments[1].category.should.equal(bodyWithAttachments.attachments[1].category); + resJson.attachments[1].path.should.equal(bodyWithAttachments.attachments[1].path); + resJson.attachments[1].type.should.equal(bodyWithAttachments.attachments[1].type); + resJson.attachments[1].tags.should.eql(bodyWithAttachments.attachments[1].tags); + + server.services.pubsub.publish.calledWith('project.draft-created').should.be.true; + // should not create phases without a template id + resJson.phases.should.have.lengthOf(0); + done(); + } + }); + }); + + it('should return 201 if valid user and data (with templateId)', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { post: () => Promise.resolve({ diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js index 5625dcef..a02010cd 100644 --- a/src/routes/projects/get.js +++ b/src/routes/projects/get.js @@ -22,8 +22,12 @@ const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); // var permissions = require('tc-core-library-js').middleware.permissions const permissions = tcMiddleware.permissions; const PROJECT_ATTRIBUTES = _.without(_.keys(models.Project.rawAttributes), 'utm', 'deletedAt'); -const PROJECT_MEMBER_ATTRIBUTES = _.concat(_.without(_.keys(models.ProjectMember.rawAttributes), 'deletedAt'), - ['firstName', 'lastName', 'handle', 'email']); +const PROJECT_MEMBER_ATTRIBUTES = _.concat(_.without(_.keys(models.ProjectMember.rawAttributes), 'deletedAt')); +// project members has some additional fields stored in ES index, which we don't have in DB +const PROJECT_MEMBER_ATTRIBUTES_ES = _.concat( + PROJECT_MEMBER_ATTRIBUTES, + ['handle'], // more fields can be added when allowed by `addUserDetailsFieldsIfAllowed` +); const PROJECT_MEMBER_INVITE_ATTRIBUTES = _.without(_.keys(models.ProjectMemberInvite.rawAttributes), 'deletedAt'); const PROJECT_ATTACHMENT_ATTRIBUTES = _.without(_.keys(models.ProjectAttachment.rawAttributes), 'deletedAt'); const PROJECT_PHASE_ATTRIBUTES = _.without( @@ -102,16 +106,13 @@ const retrieveProjectFromES = (projectId, req) => { fields = fields ? fields.split(',') : []; fields = util.parseFields(fields, { projects: PROJECT_ATTRIBUTES, - project_members: PROJECT_MEMBER_ATTRIBUTES, + project_members: util.addUserDetailsFieldsIfAllowed(PROJECT_MEMBER_ATTRIBUTES_ES, req), project_member_invites: PROJECT_MEMBER_INVITE_ATTRIBUTES, project_phases: PROJECT_PHASE_ATTRIBUTES, project_phases_products: PROJECT_PHASE_PRODUCTS_ATTRIBUTES, attachments: PROJECT_ATTACHMENT_ATTRIBUTES, }); - // if user is not admin, ignore email field for project_members - fields = util.ignoreEmailField(req, fields); - const searchCriteria = parseElasticSearchCriteria(projectId, fields) || {}; return new Promise((accept, reject) => { const es = util.getElasticSearchClient(); @@ -185,7 +186,7 @@ module.exports = [ req.log.debug('Project found in ES'); return result; }).then((project) => { - res.status(200).json(util.maskInviteEmails('$.invites[?(@.email)]', project, req)); + res.status(200).json(util.postProcessInvites('$.invites[?(@.email)]', project, req)); }) .catch(err => next(err)); }, diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js index 066c0b23..e57ce31f 100644 --- a/src/routes/projects/get.spec.js +++ b/src/routes/projects/get.spec.js @@ -1,14 +1,13 @@ /* eslint-disable no-unused-expressions */ /* eslint-disable max-len */ import chai from 'chai'; -import sinon from 'sinon'; import request from 'supertest'; import _ from 'lodash'; import config from 'config'; import models from '../../models'; -import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; +import { ATTACHMENT_TYPES } from '../../constants'; const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); @@ -88,11 +87,24 @@ const data = [ title: 'Spec', projectId: 1, description: 'specification', - filePath: 'projects/1/spec.pdf', + path: 'projects/1/spec.pdf', + type: ATTACHMENT_TYPES.FILE, + tags: ['tag1'], contentType: 'application/pdf', createdBy: 1, updatedBy: 1, }, + { + id: 2, + title: 'Link 1', + projectId: 1, + description: 'specification link', + path: 'projects/1/linkA', + type: ATTACHMENT_TYPES.LINK, + tags: ['tag2'], + createdBy: 1, + updatedBy: 1, + }, ], }, ]; @@ -257,6 +269,27 @@ describe('GET Project', () => { resJson.description.should.be.eql('es_project'); resJson.members.should.have.lengthOf(2); resJson.members[0].firstName.should.equal('es_member_1_firstName'); + + resJson.attachments.should.have.lengthOf(2); + resJson.attachments[0].id.should.eql(data[0].attachments[0].id); + resJson.attachments[0].title.should.eql(data[0].attachments[0].title); + resJson.attachments[0].projectId.should.eql(data[0].attachments[0].projectId); + resJson.attachments[0].description.should.eql(data[0].attachments[0].description); + resJson.attachments[0].path.should.eql(data[0].attachments[0].path); + resJson.attachments[0].tags.should.eql(data[0].attachments[0].tags); + resJson.attachments[0].contentType.should.eql(data[0].attachments[0].contentType); + resJson.attachments[0].createdBy.should.eql(data[0].attachments[0].createdBy); + resJson.attachments[0].updatedBy.should.eql(data[0].attachments[0].updatedBy); + + resJson.attachments[1].id.should.eql(data[0].attachments[1].id); + resJson.attachments[1].title.should.eql(data[0].attachments[1].title); + resJson.attachments[1].projectId.should.eql(data[0].attachments[1].projectId); + resJson.attachments[1].description.should.eql(data[0].attachments[1].description); + resJson.attachments[1].path.should.eql(data[0].attachments[1].path); + resJson.attachments[1].tags.should.eql(data[0].attachments[1].tags); + resJson.attachments[1].createdBy.should.eql(data[0].attachments[1].createdBy); + resJson.attachments[1].updatedBy.should.eql(data[0].attachments[1].updatedBy); + done(); } }); @@ -322,63 +355,10 @@ describe('GET Project', () => { }); }); - it('should return attachment with downloadUrl', (done) => { - models.ProjectAttachment.create({ - projectId: project1.id, - filePath: 'projects/1/spec.pdf', - contentType: 'application/pdf', - createdBy: 1, - updatedBy: 1, - name: 'spec.pdf', - description: 'blah', - }).then((attachment) => { - const mockHttpClient = { - defaults: { headers: { common: {} } }, - post: () => new Promise(resolve => resolve({ - status: 200, - data: { - result: { - status: 200, - content: { - filePath: 'projects/1/spec.pdf', - preSignedURL: 'https://www.topcoder-dev.com/downloadUrl', - }, - }, - }, - })), - }; - const spy = sinon.spy(mockHttpClient, 'post'); - const stub = sinon.stub(util, 'getHttpClient', () => mockHttpClient); - - request(server) - .get(`/v5/projects/${project1.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err, res) => { - stub.restore(); - if (err) { - done(err); - } else { - const resJson = res.body; - should.exist(resJson); - spy.should.have.been.calledOnce; - resJson.attachments.should.have.lengthOf(1); - resJson.attachments[0].filePath.should.equal(attachment.filePath); - // downloadUrl no more needed - // resJson.attachments[0].downloadUrl.should.exist; - done(); - } - }); - }); - }); - describe('URL Query fields', () => { it('should not return "email" for project members when "fields" query param is not defined (to non-admin users)', (done) => { request(server) - .get(`/v5/projects/${project1.id}?fields=members.handle`) + .get(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -397,7 +377,7 @@ describe('GET Project', () => { }); }); - it('should not return "email" for project members even if it\'s defined in "fields" query param (to non-admin users)', (done) => { + it('should not return "email" for project members even if it\'s listed in "fields" query param (to non-admin users)', (done) => { request(server) .get(`/v5/projects/${project1.id}?fields=members.email,members.handle`) .set({ @@ -441,7 +421,7 @@ describe('GET Project', () => { }); }); - it('should not return "email" for project members when "fields" query param is not defined (to admin users)', (done) => { + it('should not return "email" for project members if it\'s not listed in "fields" query param (to admin users)', (done) => { request(server) .get(`/v5/projects/${project1.id}?fields=description,members.id`) .set({ @@ -462,7 +442,7 @@ describe('GET Project', () => { }); }); - it('should return "email" for project members if it\'s defined in "fields" query param (to admin users', (done) => { + it('should return "email" for project members if it\'s listed in "fields" query param (to admin users)', (done) => { request(server) .get(`/v5/projects/${project1.id}?fields=description,members.id,members.email`) .set({ @@ -484,8 +464,7 @@ describe('GET Project', () => { }); }); - - it('should only return "id" field, when it\'s defined in "fields" query param', (done) => { + it('should only return "id" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get(`/v5/projects/${project1.id}?fields=id`) .set({ @@ -506,7 +485,7 @@ describe('GET Project', () => { }); }); - it('should only return "invites.userId" field, when it\'s defined in "fields" query param', (done) => { + it('should only return "invites.userId" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get(`/v5/projects/${project1.id}?fields=invites.userId`) .set({ @@ -527,7 +506,29 @@ describe('GET Project', () => { }); }); - it('should only return "members.role" field, when it\'s defined in "fields" query param', (done) => { + it('should not return "userId" for any invite which has "email" field', (done) => { + request(server) + .get(`/v5/projects/${project1.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.invites.length.should.be.eql(1); + resJson.invites[0].should.have.property('email'); + should.not.exist(resJson.invites[0].userId); + done(); + } + }); + }); + + it('should only return "members.role" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get(`/v5/projects/${project1.id}?fields=members.role`) .set({ @@ -548,7 +549,7 @@ describe('GET Project', () => { }); }); - it('should only return "attachments.title" field, when it\'s defined in "fields" query param', (done) => { + it('should only return "attachments.title" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get(`/v5/projects/${project1.id}?fields=attachments.title`) .set({ @@ -569,7 +570,7 @@ describe('GET Project', () => { }); }); - it('should only return "phases.name" field, when it\'s defined in "fields" query param', (done) => { + it('should only return "phases.name" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get(`/v5/projects/${project1.id}?fields=phases.name`) .set({ @@ -590,9 +591,9 @@ describe('GET Project', () => { }); }); - it('should only return "phases.products.name" field, when it\'s defined in "fields" query param and "phases" is also defined', (done) => { + it('should only return "phases.products.name" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) - .get(`/v5/projects/${project1.id}?fields=phases.products.name,phases.name`) + .get(`/v5/projects/${project1.id}?fields=phases.products.name`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index 8ca1279a..f53514cc 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -26,10 +26,12 @@ const PROJECT_ATTRIBUTES = _.without(_.keys(models.Project.rawAttributes), 'utm', 'deletedAt', ); -const PROJECT_MEMBER_ATTRIBUTES = _.concat(_.without( - _.keys(models.ProjectMember.rawAttributes), - 'deletedAt', -), ['firstName', 'lastName', 'handle', 'email']); +const PROJECT_MEMBER_ATTRIBUTES = _.without(_.keys(models.ProjectMember.rawAttributes)); +// project members has some additional fields stored in ES index, which we don't have in DB +const PROJECT_MEMBER_ATTRIBUTES_ES = _.concat( + PROJECT_MEMBER_ATTRIBUTES, + ['handle'], // more fields can be added when allowed by `addUserDetailsFieldsIfAllowed` +); const PROJECT_MEMBER_INVITE_ATTRIBUTES = _.without( _.keys(models.ProjectMemberInvite.rawAttributes), 'deletedAt', @@ -60,7 +62,14 @@ const SUPPORTED_FILTERS = [ 'directProjectId', ]; -const escapeEsKeyword = keyword => keyword.replace(/[+-=> keyword.replace(/[+-=> { let should = [ @@ -426,7 +435,7 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { if (!keyword) { // Not a specific field search nor an exact phrase search, do a wildcard match - keyword = criteria.filters.keyword; + keyword = escapeEsKeyword(keywordCriterion); matchType = MATCH_TYPE_WILDCARD; } @@ -544,17 +553,13 @@ const retrieveProjects = (req, criteria, sort, ffields) => { // parse the fields string to determine what fields are to be returned fields = util.parseFields(fields, { projects: PROJECT_ATTRIBUTES, - project_members: PROJECT_MEMBER_ATTRIBUTES, + project_members: util.addUserDetailsFieldsIfAllowed(PROJECT_MEMBER_ATTRIBUTES_ES, req), project_member_invites: PROJECT_MEMBER_INVITE_ATTRIBUTES, project_phases: PROJECT_PHASE_ATTRIBUTES, project_phases_products: PROJECT_PHASE_PRODUCTS_ATTRIBUTES, attachments: PROJECT_ATTACHMENT_ATTRIBUTES, }); - - // if user is not admin, ignore email field for project_members - fields = util.ignoreEmailField(req, fields); - // make sure project.id is part of fields if (_.indexOf(fields.projects, 'id') < 0) { fields.projects.push('id'); @@ -624,15 +629,18 @@ module.exports = [ // so we don't want DB to return unrelated data, ref issue #450 if (_.intersection(_.keys(filters), SUPPORTED_FILTERS).length > 0) { req.log.debug('Don\'t fallback to DB because some filters are defined.'); - return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req)); + return util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', result, req)); } return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) - .then(r => util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', r, req))); + .then(r => util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', r, req))); } req.log.debug('Projects found in ES'); // set header - return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req)); + return util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', result, req)); }) .catch(err => next(err)); } @@ -650,14 +658,17 @@ module.exports = [ // so we don't want DB to return unrelated data, ref issue #450 if (_.intersection(_.keys(filters), SUPPORTED_FILTERS).length > 0) { req.log.debug('Don\'t fallback to DB because some filters are defined.'); - return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req)); + return util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', result, req)); } return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) - .then(r => util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', r, req))); + .then(r => util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', r, req))); } req.log.debug('Projects found in ES'); - return util.setPaginationHeaders(req, res, util.maskInviteEmails('$[*].invites[?(@.email)]', result, req)); + return util.setPaginationHeaders(req, res, + util.postProcessInvites('$.rows[*].invites[?(@.email)]', result, req)); }) .catch(err => next(err)); }, diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index 9ebb3e13..c264979e 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -8,6 +8,7 @@ import config from 'config'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; +import { ATTACHMENT_TYPES } from '../../constants'; const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); @@ -21,7 +22,7 @@ const data = [ type: 'generic', billingAccountId: 1, name: 'test1', - description: 'test project1', + description: 'test project1 abc/d', status: 'active', details: { utm: { @@ -64,6 +65,12 @@ const data = [ email: 'test@topcoder.com', status: 'pending', }, + { + id: 2, + email: 'hello@world.com', + status: 'pending', + createdBy: 1, + }, ], phases: [ @@ -87,11 +94,24 @@ const data = [ title: 'Spec', projectId: 1, description: 'specification', - filePath: 'projects/1/spec.pdf', + path: 'projects/1/spec.pdf', + type: ATTACHMENT_TYPES.FILE, + tags: ['tag1'], contentType: 'application/pdf', createdBy: 1, updatedBy: 1, }, + { + id: 2, + title: 'Link 1', + projectId: 1, + description: 'specification link', + path: 'projects/1/linkA', + type: ATTACHMENT_TYPES.LINK, + tags: ['tag2'], + createdBy: 1, + updatedBy: 1, + }, ], }, { @@ -160,7 +180,7 @@ const data = [ role: 'manager', firstName: 'first', lastName: 'last', - handle: 'manager_handle', + handle: 'MANAGER_HANDLE', isPrimary: true, createdBy: 1, updatedBy: 1, @@ -216,7 +236,9 @@ describe('LIST Project', () => { title: 'Spec', projectId: project1.id, description: 'specification', - filePath: 'projects/1/spec.pdf', + path: 'projects/1/spec.pdf', + type: ATTACHMENT_TYPES.FILE, + tags: ['tag1'], contentType: 'application/pdf', createdBy: 1, updatedBy: 1, @@ -277,6 +299,9 @@ describe('LIST Project', () => { }); return Promise.all([p1, p2, p3]).then(() => { + data[0].id = project1.id; + data[1].id = project2.id; + data[2].id = project3.id; const esp1 = server.services.es.index({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, @@ -355,7 +380,7 @@ describe('LIST Project', () => { should.exist(resJson); resJson.should.have.lengthOf(1); // since project 2 is indexed with id 2 - resJson[0].id.should.equal(2); + resJson[0].id.should.equal(project2.id); done(); } }); @@ -417,18 +442,32 @@ describe('LIST Project', () => { const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); - resJson[0].should.have.property('attachments'); - resJson[0].attachments.should.have.lengthOf(1); - resJson[0].attachments[0].should.have.property('id'); - resJson[0].attachments[0].should.have.property('projectId'); - resJson[0].attachments[0].should.have.property('title'); - resJson[0].attachments[0].should.have.property('description'); - resJson[0].attachments[0].should.have.property('filePath'); - resJson[0].attachments[0].should.have.property('contentType'); - resJson[0].attachments[0].should.have.property('createdBy'); - resJson[0].attachments[0].should.have.property('updatedBy'); - resJson[0].should.have.property('description'); - resJson[0].should.have.property('billingAccountId'); + const project = _.find(resJson, { id: project1.id }); + project.should.have.property('attachments'); + project.attachments.should.have.lengthOf(2); + project.attachments[0].should.have.property('id'); + project.attachments[0].should.have.property('projectId'); + project.attachments[0].should.have.property('title'); + project.attachments[0].should.have.property('description'); + project.attachments[0].should.have.property('path'); + project.attachments[0].should.have.property('type'); + project.attachments[0].should.have.property('tags'); + project.attachments[0].should.have.property('contentType'); + project.attachments[0].should.have.property('createdBy'); + project.attachments[0].should.have.property('updatedBy'); + + project.attachments[1].should.have.property('id'); + project.attachments[1].should.have.property('projectId'); + project.attachments[1].should.have.property('title'); + project.attachments[1].should.have.property('description'); + project.attachments[1].should.have.property('path'); + project.attachments[1].should.have.property('type'); + project.attachments[1].should.have.property('tags'); + project.attachments[1].should.have.property('createdBy'); + project.attachments[1].should.have.property('updatedBy'); + + project.should.have.property('description'); + project.should.have.property('billingAccountId'); done(); } }); @@ -449,9 +488,10 @@ describe('LIST Project', () => { const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); - resJson[0].should.have.property('attachments'); - resJson[0].should.have.property('description'); - resJson[0].should.have.property('billingAccountId'); + const project = _.find(resJson, p => p.id === project1.id); + project.should.have.property('attachments'); + project.should.have.property('description'); + project.should.have.property('billingAccountId'); done(); } }); @@ -472,16 +512,17 @@ describe('LIST Project', () => { const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); - resJson[0].should.have.property('id'); - resJson[0].should.have.property('type'); - resJson[0].should.have.property('billingAccountId'); - resJson[0].should.have.property('description'); - resJson[0].should.have.property('status'); - resJson[0].should.have.property('details'); - resJson[0].should.have.property('createdBy'); - resJson[0].should.have.property('updatedBy'); - resJson[0].should.have.property('members'); - resJson[0].should.have.property('attachments'); + const project = _.find(resJson, p => p.id === project1.id); + project.should.have.property('id'); + project.should.have.property('type'); + project.should.have.property('billingAccountId'); + project.should.have.property('description'); + project.should.have.property('status'); + project.should.have.property('details'); + project.should.have.property('createdBy'); + project.should.have.property('updatedBy'); + project.should.have.property('members'); + project.should.have.property('attachments'); done(); } }); @@ -571,7 +612,7 @@ describe('LIST Project', () => { it('should return project that match when filtering by id (exact)', (done) => { request(server) - .get('/v5/projects/?id=1') + .get(`/v5/projects/?id=${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -584,7 +625,7 @@ describe('LIST Project', () => { const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); - resJson[0].id.should.equal(1); + resJson[0].id.should.equal(project1.id); resJson[0].name.should.equal('test1'); done(); } @@ -723,7 +764,7 @@ describe('LIST Project', () => { }); }); - it('should return all projects that match when filtering by customer handle', (done) => { + it('should return all projects that match when filtering by customer handle (lowercase)', (done) => { request(server) .get('/v5/projects/?customer=*tourist*') .set({ @@ -746,7 +787,53 @@ describe('LIST Project', () => { }); }); - it('should return all projects that match when filtering by manager handle', (done) => { + it('should return all projects that match when filtering by customer handle (uppercase)', (done) => { + request(server) + .get('/v5/projects/?customer=*TOUR*') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + resJson[0].name.should.equal('test1'); + resJson[0].members.should.have.deep.property('[0].role', 'customer'); + resJson[0].members[0].userId.should.equal(40051331); + done(); + } + }); + }); + + it('should return all projects that match when filtering by customer handle (mixed case)', (done) => { + request(server) + .get('/v5/projects/?customer=*tOURiS*') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + resJson[0].name.should.equal('test1'); + resJson[0].members.should.have.deep.property('[0].role', 'customer'); + resJson[0].members[0].userId.should.equal(40051331); + done(); + } + }); + }); + + it('should return all projects that match when filtering by manager handle (lowercase)', (done) => { request(server) .get('/v5/projects/?manager=*_handle') .set({ @@ -769,6 +856,52 @@ describe('LIST Project', () => { }); }); + it('should return all projects that match when filtering by manager handle (uppercase)', (done) => { + request(server) + .get('/v5/projects/?manager=MANAG*') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + resJson[0].name.should.equal('test3'); + resJson[0].members.should.have.deep.property('[0].role', 'manager'); + resJson[0].members[0].userId.should.equal(40051334); + done(); + } + }); + }); + + it('should return all projects that match when filtering by manager handle (mixed case)', (done) => { + request(server) + .get('/v5/projects/?manager=*_HAndLe') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + resJson[0].name.should.equal('test3'); + resJson[0].members.should.have.deep.property('[0].role', 'manager'); + resJson[0].members[0].userId.should.equal(40051334); + done(); + } + }); + }); + it('should return all projects that match when filtering by manager, searching on any non-customer role', (done) => { request(server) .get('/v5/projects/?manager=copi*') @@ -978,6 +1111,9 @@ describe('LIST Project', () => { should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); + resJson[0].invites.should.have.lengthOf(2); + resJson[0].invites[0].should.have.property('email'); + resJson[0].invites[1].email.should.equal('h***o@w***d.com'); done(); } }); @@ -1026,7 +1162,7 @@ describe('LIST Project', () => { }); - it('should not return "email" for project members even if it\'s defined in "fields" query param (to non-admin users)', (done) => { + it('should not return "email" for project members even if it\'s listed in "fields" query param (to non-admin users)', (done) => { request(server) .get('/v5/projects/?fields=members.email,members.id') .set({ @@ -1065,13 +1201,13 @@ describe('LIST Project', () => { resJson.should.have.lengthOf(1); resJson[0].should.have.property('description'); resJson[0].should.not.have.property('cancelReason'); - resJson[0].description.should.be.eq('test project1'); + resJson[0].description.should.be.eq('test project1 abc/d'); done(); } }); }); - it('should not return "email" for project members when "fields" query param is not defined (to admin users)', (done) => { + it('should not return "email" for project members when it is not listed in "fields" query param (to admin users)', (done) => { request(server) .get('/v5/projects/?fields=description,members.id') .set({ @@ -1085,7 +1221,7 @@ describe('LIST Project', () => { } else { const resJson = res.body; should.exist(resJson); - const project = _.find(resJson, p => p.id === 1); + const project = _.find(resJson, p => p.id === project1.id); const member = _.find(project.members, m => m.id === 1); member.should.not.have.property('email'); done(); @@ -1094,7 +1230,7 @@ describe('LIST Project', () => { }); - it('should return "email" for project members if it\'s defined in "fields" query param (to admin users', (done) => { + it('should return "email" for project members if it\'s listed in "fields" query param (to admin users)', (done) => { request(server) .get('/v5/projects/?fields=description,members.id,members.email') .set({ @@ -1108,7 +1244,7 @@ describe('LIST Project', () => { } else { const resJson = res.body; should.exist(resJson); - const project = _.find(resJson, p => p.id === 1); + const project = _.find(resJson, p => p.id === project1.id); const member = _.find(project.members, m => m.id === 1); member.should.have.property('email'); member.email.should.be.eq('test@test.com'); @@ -1117,7 +1253,7 @@ describe('LIST Project', () => { }); }); - it('should only return "id" field, when it\'s defined in "fields" query param', (done) => { + it('should only return "id" field, when it\'s the only fields listed in "fields" query param', (done) => { request(server) .get('/v5/projects/?fields=id') .set({ @@ -1138,7 +1274,7 @@ describe('LIST Project', () => { }); }); - it('should only return "invites.userId" field, when it\'s defined in "fields" query param', (done) => { + it('should only return "invites.userId" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get('/v5/projects/?fields=invites.userId') .set({ @@ -1152,14 +1288,15 @@ describe('LIST Project', () => { } else { const resJson = res.body; should.exist(resJson); - resJson[0].invites[0].should.have.property('userId'); - _.keys(resJson[0].invites[0]).length.should.be.eq(1); + const project = _.find(resJson, p => p.id === project1.id); + project.invites[0].should.have.property('userId'); + _.keys(project.invites[0]).length.should.be.eq(1); done(); } }); }); - it('should only return "members.role" field, when it\'s defined in "fields" query param', (done) => { + it('should only return "members.role" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get('/v5/projects/?fields=members.role') .set({ @@ -1173,14 +1310,15 @@ describe('LIST Project', () => { } else { const resJson = res.body; should.exist(resJson); - resJson[0].members[0].should.have.property('role'); - _.keys(resJson[0].members[0]).length.should.be.eq(1); + const project = _.find(resJson, p => p.id === project1.id); + project.members[0].should.have.property('role'); + _.keys(project.members[0]).length.should.be.eq(1); done(); } }); }); - it('should only return "attachments.title" field, when it\'s defined in "fields" query param', (done) => { + it('should only return "attachments.title" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get('/v5/projects/?fields=attachments.title') .set({ @@ -1194,14 +1332,15 @@ describe('LIST Project', () => { } else { const resJson = res.body; should.exist(resJson); - resJson[0].attachments[0].should.have.property('title'); - _.keys(resJson[0].attachments[0]).length.should.be.eq(1); + const project = _.find(resJson, p => p.id === project1.id); + project.attachments[0].should.have.property('title'); + _.keys(project.attachments[0]).length.should.be.eq(1); done(); } }); }); - it('should only return "phases.name" field, when it\'s defined in "fields" query param', (done) => { + it('should only return "phases.name" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) .get('/v5/projects/?fields=phases.name') .set({ @@ -1215,7 +1354,7 @@ describe('LIST Project', () => { } else { const resJson = res.body; should.exist(resJson); - const project = _.find(resJson, p => p.id === 1); + const project = _.find(resJson, p => p.id === project1.id); project.phases[0].should.have.property('name'); _.keys(project.phases[0]).length.should.be.eq(1); done(); @@ -1223,9 +1362,9 @@ describe('LIST Project', () => { }); }); - it('should only return "phases.products.name" field, when it\'s defined in "fields" query param and "phases" is also defined', (done) => { + it('should only return "phases.products.name" field, when it\'s the only field listed in "fields" query param', (done) => { request(server) - .get('/v5/projects/?fields=phases.products.name,phases.name') + .get('/v5/projects/?fields=phases.products.name') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -1237,13 +1376,53 @@ describe('LIST Project', () => { } else { const resJson = res.body; should.exist(resJson); - const project = _.find(resJson, p => p.id === 1); + const project = _.find(resJson, p => p.id === project1.id); project.phases[0].products[0].should.have.property('name'); _.keys(project.phases[0].products[0]).length.should.be.eq(1); done(); } }); }); + + it('should find a project by quoted keyword with a special symbol in the name', (done) => { + request(server) + .get('/v5/projects/?keyword="abc/d"') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + done(); + } + }); + }); + + it('should find a project by keyword with a special symbol in the name', (done) => { + request(server) + .get('/v5/projects/?keyword=abc/d') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.should.have.lengthOf(1); + done(); + } + }); + }); }); }); }); diff --git a/src/services/lookerService.js b/src/services/lookerService.js index fe6316dc..0401eb81 100644 --- a/src/services/lookerService.js +++ b/src/services/lookerService.js @@ -110,7 +110,7 @@ function generateEmbedUrl(authUser, project, member, reportUrl) { group_ids: [], first_name: member.firstName, last_name: member.lastName, - permissions: ['access_data', 'see_looks', 'see_user_dashboards'], + permissions: ['access_data', 'see_looks', 'see_user_dashboards', 'schedule_look_emails', 'download_with_limit'], models: ['projects_tc_employees'], access_filters: { projects_tc_employees: { diff --git a/src/tests/seed.js b/src/tests/seed.js index ffa5fb0f..60bfe4d6 100644 --- a/src/tests/seed.js +++ b/src/tests/seed.js @@ -1,5 +1,5 @@ import models from '../models'; -import { TIMELINE_REFERENCES } from '../constants'; +import { TIMELINE_REFERENCES, ATTACHMENT_TYPES } from '../constants'; models.sequelize.sync({ force: true }) .then(() => @@ -157,7 +157,8 @@ models.sequelize.sync({ force: true }) title: 'Spec', projectId: project1.id, description: 'specification', - filePath: 'projects/1/spec.pdf', + path: 'projects/1/spec.pdf', + type: ATTACHMENT_TYPES.FILE, contentType: 'application/pdf', createdBy: 1, updatedBy: 1, diff --git a/src/util.js b/src/util.js index e76e4a9e..0a773e8d 100644 --- a/src/util.js +++ b/src/util.js @@ -16,7 +16,7 @@ import config from 'config'; import urlencode from 'urlencode'; import elasticsearch from 'elasticsearch'; import AWS from 'aws-sdk'; -// import jp from 'jsonpath'; +import jp from 'jsonpath'; import Promise from 'bluebird'; import models from './models'; @@ -262,29 +262,24 @@ _.assignIn(util, { } return fields; }, + /** - * Remove email field for PROJECT_MEMBER_ATTRIBUTES, if user is not admin - * @param {object} req request object - * @param {object} fields fields object - * @return {object} the parsed array + * Add user details fields to the list of field, if it's allowed to a user who made the request + * + * @param {Array} fields fields list + * @param {Object} req request object + * + * @return {Array} fields list with 'email' if allowed */ - ignoreEmailField: (req, fields) => { - if (!fields.project_members) { - return fields; - } - - // Only Topcoder Admins can get all the fields + addUserDetailsFieldsIfAllowed: (fields, req) => { + // Only Topcoder Admins can get email if (util.hasPermission({ topcoderRoles: [USER_ROLE.TOPCODER_ADMIN] }, req.authUser)) { - return fields; + return _.concat(fields, ['email', 'firstName', 'lastName']); } - // for non topcoder admins remove emails from the field list - _.assign(fields, { project_members: _.filter(fields.project_members, f => f !== 'email') }); - _.assign(fields, { project_members: _.filter(fields.project_members, f => f !== 'firstName') }); - _.assign(fields, { project_members: _.filter(fields.project_members, f => f !== 'lastName') }); - return fields; }, + /** * Parse the query filters * @param {String} fqueryFilter the query filter string @@ -376,10 +371,7 @@ _.assignIn(util, { if (resp.status !== 200 || resp.data.result.status !== 200) { return Promise.reject(new Error('Unable to fetch pre-signed url')); } - return [ - filePath, - resp.data.result.content.preSignedURL, - ]; + return resp.data.result.content.preSignedURL; }); }, getProjectAttachments: (req, projectId) => { @@ -580,6 +572,35 @@ _.assignIn(util, { } }), + /** + * Retrieve member details from user handles + */ + getMemberDetailsByHandles: Promise.coroutine(function* (handles, logger, requestId) { // eslint-disable-line func-names + if (_.isNil(handles) || (_.isArray(handles) && handles.length <= 0)) { + return Promise.resolve([]); + } + try { + const token = yield this.getM2MToken(); + const httpClient = this.getHttpClient({ id: requestId, log: logger }); + if (logger) { + logger.trace(handles); + } + const handleArr = _.map(handles, h => `handleLower:${h.toLowerCase()}`); + return httpClient.get(`${config.memberServiceEndpoint}/_search`, { + params: { + query: `${handleArr.join(urlencode(' OR ', 'utf8'))}`, + fields: 'userId,handle,firstName,lastName,email', + }, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }).then(res => _.get(res, 'data.result.content', null)); + } catch (err) { + return Promise.reject(err); + } + }), + /** * maksEmail * @@ -590,67 +611,96 @@ _.assignIn(util, { maskEmail: (email) => { // common function for formating const addMask = (str) => { - let newStr; const len = str.length; - if (len <= 3) { - newStr = _.repeat('*', len); - } else { - newStr = str.substr(0, 2) + _.repeat('*', len - 3) + str.substr(-1); + if (len === 1) { + return `${str}***${str}`; } - return newStr; + return `${str[0]}***${str[len - 1]}`; }; try { const mailParts = email.split('@'); - const domainParts = mailParts[1].split('.'); let userName = mailParts[0]; userName = addMask(userName); mailParts[0] = userName; - let domainName = domainParts[0]; - domainName = addMask(domainName); - domainParts[0] = domainName; + const index = mailParts[1].lastIndexOf('.'); + if (index !== -1) { + mailParts[1] = `${addMask(mailParts[1].slice(0, index))}.${mailParts[1].slice(index + 1)}`; + } - mailParts[1] = domainParts.join('.'); return mailParts.join('@'); } catch (e) { return email; } }, /** - * Filter member details by input fields + * Post-process given invite(s) + * Mask `email` and hide `userId` to prevent leaking Personally Identifiable Information (PII) + * + * Immutable - doesn't modify data, but creates a clone. * - * @param {String} jsonPath jsonpath string + * @param {String} jsonPath jsonpath string * @param {Object} data the data which need to process * @param {Object} req The request object * * @return {Object} data has been processed */ - maskInviteEmails: (jsonPath, data, req) => { // eslint-disable-line - // temporary disable this feature, because it has some side effects - // see relative issues: - // - https://github.com/topcoder-platform/tc-project-service/issues/420 - // - https://github.com/appirio-tech/connect-app/issues/3412 - // - https://github.com/topcoder-platform/tc-project-service/issues/422 - // - https://github.com/appirio-tech/connect-app/issues/3413 - // uncomment code below, to enable masking emails again - - /* + postProcessInvites: (jsonPath, data, req) => { + // clone data to avoid mutations + const dataClone = _.cloneDeep(data); + const isAdmin = util.hasPermission({ topcoderRoles: [USER_ROLE.TOPCODER_ADMIN] }, req.authUser); + const currentUserId = req.authUser.userId; + + // admins can get data as it is if (isAdmin) { - return data; + // even though we didn't make any changes to the data, return a clone here for consistency + return dataClone; } - jp.apply(data, jsonPath, (value) => { + + const postProcessInvite = (invite) => { + if (!_.has(invite, 'email')) { + return invite; + } + + if (invite.email) { + const canSeeEmail = ( + isAdmin || // admin + invite.createdBy === currentUserId || // user who created invite + invite.userId === currentUserId // user who is invited + ); + // mask email if user cannot see it + _.assign(invite, { + email: canSeeEmail ? invite.email : util.maskEmail(invite.email), + }); + + const canGetUserId = ( + isAdmin || // admin + invite.userId === currentUserId // user who is invited + ); + if (invite.userId && !canGetUserId) { + _.assign(invite, { + userId: null, + }); + } + } + + return invite; + }; + + jp.apply(dataClone, jsonPath, (value) => { if (_.isObject(value)) { - _.assign(value, { email: util.maskEmail(value.email) }); - return value; + // data contains nested invite object + return postProcessInvite(value); } - // isString or null - return util.maskEmail(value); + // data is single invite object + // value is string or null + return postProcessInvite(dataClone).email; }); - */ - return data; + + return dataClone; }, /** @@ -669,7 +719,7 @@ _.assignIn(util, { const memberTraitFields = ['photoURL', 'workingHourStart', 'workingHourEnd', 'timeZone']; let memberDetailFields = ['handle']; - // Only Topcoder admins can get emails for users + // Only Topcoder admins can get emails, first and last name for users if (util.hasPermission({ topcoderRoles: [USER_ROLE.TOPCODER_ADMIN] }, req.authUser)) { memberDetailFields = memberDetailFields.concat(['email', 'firstName', 'lastName']); } @@ -726,27 +776,6 @@ _.assignIn(util, { return _.map(members, (member) => { let memberDetails = _.find(allMemberDetails, ({ userId }) => userId === member.userId); memberDetails = _.assign({}, member, _.pick(memberDetails, _.union(memberDetailFields, memberTraitFields))); - - // in general, only users with Topcoder administrator privileges can see emails - let canSeeEmail = util.hasPermission({ topcoderRoles: [USER_ROLE.TOPCODER_ADMIN] }, req.authUser); - // we also shouldn't return full name to users except of admins - const canSeeFullName = util.hasPermission({ topcoderRoles: [USER_ROLE.TOPCODER_ADMIN] }, req.authUser); - - // specially for invite objects, we still have to return email, if invite is for a new user which doesn't have "userId" - if (memberDetails.status) { // we identify that the object is "invite" and not a "member" if object has "status" field - canSeeEmail = canSeeEmail || !memberDetails.userId; - } - - if (!canSeeEmail) { - delete memberDetails.email; - } - - // this is a temporary fix as ES also has this data, so we have explicitly remove it - if (!canSeeFullName) { - delete memberDetails.firstName; - delete memberDetails.lastName; - } - return _(memberDetails).pick(fields).defaults(memberDefaults).value(); }); }, diff --git a/src/util.spec.js b/src/util.spec.js index f3fc506f..0fe574ed 100644 --- a/src/util.spec.js +++ b/src/util.spec.js @@ -14,36 +14,36 @@ describe('Util method', () => { it('should return the original value if the email is non-email string', () => { util.maskEmail('aa.com').should.equal('aa.com'); }); - it('should return "*@*.com" if the email is "a@a.com"', () => { - util.maskEmail('a@a.com').should.equal('*@*.com'); + it('should return "a***a@a***a.com" if the email is "a@a.com"', () => { + util.maskEmail('a@a.com').should.equal('a***a@a***a.com'); }); - it('should return "**@**.com" if the email is "ab@aa.com"', () => { - util.maskEmail('ab@aa.com').should.equal('**@**.com'); + it('should return "a***b@a***a.com" if the email is "ab@aa.com"', () => { + util.maskEmail('ab@aa.com').should.equal('a***b@a***a.com'); }); it('should return "***@***.com" if the email is "abc@aaa.com"', () => { - util.maskEmail('abc@aaa.com').should.equal('***@***.com'); + util.maskEmail('abc@aaa.com').should.equal('a***c@a***a.com'); }); it('should return "ab*d@aa*a.com" if the email is "abcd@aaaa.com"', () => { - util.maskEmail('abcd@aaaa.com').should.equal('ab*d@aa*a.com'); + util.maskEmail('abcd@aaaa.com').should.equal('a***d@a***a.com'); }); it('should return "ab**e@aa**a.com" if the email is "abcde@aaaaa.com"', () => { - util.maskEmail('abcde@aaaaa.com').should.equal('ab**e@aa**a.com'); + util.maskEmail('abcde@aaaaa.com').should.equal('a***e@a***a.com'); }); it('should return "ab***f@aa***a.com" if the email is "abcdef@aaaaaa.com"', () => { - util.maskEmail('abcdef@aaaaaa.com').should.equal('ab***f@aa***a.com'); + util.maskEmail('abcdef@aaaaaa.com').should.equal('a***f@a***a.com'); }); it('should return "ab****g@aa****a.com" if the email is "abcdefg@aaaaaaa.com"', () => { - util.maskEmail('abcdefg@aaaaaaa.com').should.equal('ab****g@aa****a.com'); + util.maskEmail('abcdefg@aaaaaaa.com').should.equal('a***g@a***a.com'); }); it('should return "ab*****h@aa****a.com" if the email is "abcdefgh@aaaaaaaa.com"', () => { - util.maskEmail('abcdefgh@aaaaaaaa.com').should.equal('ab*****h@aa*****a.com'); + util.maskEmail('abcdefgh@aaaaaaaa.com').should.equal('a***h@a***a.com'); }); it('should return "ab******i@aa*****a.com" if the email is "abcdefghi@aaaaaaaaa.com"', () => { - util.maskEmail('abcdefghi@aaaaaaaaa.com').should.equal('ab******i@aa******a.com'); + util.maskEmail('abcdefghi@aaaaaaaaa.com').should.equal('a***i@a***a.com'); }); }); - xdescribe('maskInviteEmails', () => { + describe('postProcessInvites', () => { it('should mask emails when passing data like for a project list endpoint for non-admin user', () => { const list = [ { @@ -60,7 +60,7 @@ describe('Util method', () => { id: 1, invites: [{ id: 2, - email: 'ab*d@aa*a.com', + email: 'a***d@a***a.com', }, ], }, @@ -68,8 +68,9 @@ describe('Util method', () => { const res = { authUser: { userId: 2 }, }; - util.maskInviteEmails('$..invites[?(@.email)]', list, res).should.deep.equal(list2); + util.postProcessInvites('$..invites[?(@.email)]', list, res).should.deep.equal(list2); }); + it('should mask emails when passing data like for a project details endpoint for non-admin user', () => { const detail = { id: 1, @@ -83,14 +84,14 @@ describe('Util method', () => { id: 1, invites: [{ id: 2, - email: 'ab*d@aa*a.com', + email: 'a***d@a***a.com', }, ], }; const res = { authUser: { userId: 2 }, }; - util.maskInviteEmails('$..invites[?(@.email)]', detail, res).should.deep.equal(detail2); + util.postProcessInvites('$..invites[?(@.email)]', detail, res).should.deep.equal(detail2); }); it('should mask emails when passing data like for a single invite endpoint for non-admin user', () => { @@ -106,14 +107,14 @@ describe('Util method', () => { success: [ { id: 1, - email: 'ab*d@aa*a.com', + email: 'a***d@a***a.com', }, ], }; const res = { authUser: { userId: 2 }, }; - util.maskInviteEmails('$.success[?(@.email)]', detail, res).should.deep.equal(detail2); + util.postProcessInvites('$.success[?(@.email)]', detail, res).should.deep.equal(detail2); }); it('should NOT mask emails when passing data like for a single invite endpoint for admin user', () => { @@ -136,7 +137,84 @@ describe('Util method', () => { const res = { authUser: { userId: 2, roles: ['administrator'] }, }; - util.maskInviteEmails('$..email', detail, res).should.deep.equal(detail2); + util.postProcessInvites('$.success[?(@.email)]', detail, res).should.deep.equal(detail2); + }); + + it('should NOT mask emails when passing data like for a single invite endpoint for user\'s own invite', () => { + const detail = { + success: [ + { + id: 1, + email: 'abcd@aaaa.com', + createdBy: 2, + }, + ], + }; + const detail2 = { + success: [ + { + id: 1, + email: 'abcd@aaaa.com', + createdBy: 2, + }, + ], + }; + const res = { + authUser: { userId: 2, email: 'abcd@aaaa.com' }, + }; + util.postProcessInvites('$.success[?(@.email)]', detail, res).should.deep.equal(detail2); + }); + + it('should NOT mask emails when passing data like for a project details endpoint for user\'s own invite', () => { + const detail = { + id: 1, + invites: [{ + id: 2, + email: 'abcd@aaaa.com', + createdBy: 2, + }, + ], + }; + const detail2 = { + id: 1, + invites: [{ + id: 2, + email: 'abcd@aaaa.com', + createdBy: 2, + }, + ], + }; + const res = { + authUser: { userId: 2, email: 'abcd@aaaa.com' }, + }; + util.postProcessInvites('$.invites[?(@.email)]', detail, res).should.deep.equal(detail2); + }); + + it('should not return `userId` for invite by email', () => { + const detail = { + id: 1, + invites: [{ + id: 2, + email: 'abcd@aaaa.com', + userId: 33, + createdBy: 2, + }, + ], + }; + const detail2 = { + id: 1, + invites: [{ + id: 2, + email: 'abcd@aaaa.com', + userId: null, + createdBy: 2, + }, + ], + }; + const res = { + authUser: { userId: 2 }, + }; + util.postProcessInvites('$..invites[?(@.email)]', detail, res).should.deep.equal(detail2); }); }); diff --git a/src/utils/es-config.js b/src/utils/es-config.js index 657add4b..24357857 100644 --- a/src/utils/es-config.js +++ b/src/utils/es-config.js @@ -69,7 +69,13 @@ MAPPINGS[ES_PROJECT_INDEX] = { description: { type: 'string', }, - filePath: { + path: { + type: 'string', + }, + type: { + type: 'string', + }, + tags: { type: 'string', }, id: { @@ -241,7 +247,6 @@ MAPPINGS[ES_PROJECT_INDEX] = { }, handle: { type: 'string', - index: 'not_analyzed', }, id: { type: 'long',