From 5b880c0270960fdf024610eb9ab676d1df8171f4 Mon Sep 17 00:00:00 2001 From: Sharathkumar Anbu Date: Wed, 5 Jun 2019 23:59:35 +0530 Subject: [PATCH 01/88] Update Swagger to V5 standards --- postman.json => docs/postman.json | 0 .../postman_environment.json | 0 swagger.yaml => docs/swagger.yaml | 2927 ++++---- package-lock.json | 6298 +++++++++++------ package.json | 12 +- 5 files changed, 5425 insertions(+), 3812 deletions(-) rename postman.json => docs/postman.json (100%) rename postman_environment.json => docs/postman_environment.json (100%) rename swagger.yaml => docs/swagger.yaml (72%) diff --git a/postman.json b/docs/postman.json similarity index 100% rename from postman.json rename to docs/postman.json diff --git a/postman_environment.json b/docs/postman_environment.json similarity index 100% rename from postman_environment.json rename to docs/postman_environment.json diff --git a/swagger.yaml b/docs/swagger.yaml similarity index 72% rename from swagger.yaml rename to docs/swagger.yaml index 27d458c1..aedaa356 100644 --- a/swagger.yaml +++ b/docs/swagger.yaml @@ -1,9 +1,18 @@ swagger: '2.0' info: - version: v4 + version: v5 title: Projects API + description: | + Project API + + ### Pagination + Requests that return multiple items will be paginated to 20 items by default. + You can specify further pages with the `page` parameter. + You can also set a custom page size up to 100 with the `perPage` parameter. + + Pagination response data is included in http headers. By Default, the response header contains links with `next`, `last`, `first`, `prev` resource links. host: 'localhost:3000' -basePath: /v4 +basePath: /v5 schemes: - http produces: @@ -28,33 +37,55 @@ paths: '200': description: A list of projects schema: - $ref: '#/definitions/ProjectListResponse' - '403': - description: No permission or wrong token + type: array + items: + $ref: '#/definitions/Project' + headers: + X-Next-Page: + type: integer + description: The index of the next page + X-Page: + type: integer + description: The index of the current page (starting at 1) + X-Per-Page: + type: integer + description: The number of items to list per page + X-Prev-Page: + type: integer + description: The index of the previous page + X-Total: + type: integer + description: The total number of items + X-Total-Pages: + type: integer + description: The total number of pages + Link: + type: string + description: Pagination link header. + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: - - $ref: '#/parameters/offsetParam' - - $ref: '#/parameters/limitParam' - - name: filter - required: true - type: string - in: query - description: | - Url encoded list of Supported filters - - id - - status - - type - - memberOnly - - keyword - - name - - code - - customer - - manager + - $ref: '#/parameters/pageParam' + - $ref: '#/parameters/perPageParam' + - $ref: '#/parameters/idQueryParam' + - $ref: '#/parameters/statusQueryParam' + - $ref: '#/parameters/typeQueryParam' + - $ref: '#/parameters/memberOnlyQueryParam' + - $ref: '#/parameters/keywordQueryParam' + - $ref: '#/parameters/nameQueryParam' + - $ref: '#/parameters/codeQueryParam' + - $ref: '#/parameters/customerQueryParam' + - $ref: '#/parameters/managerQueryParam' - name: sort required: false description: > @@ -74,33 +105,55 @@ paths: '200': description: A list of projects schema: - $ref: '#/definitions/ProjectListResponse' - '403': - description: No permission or wrong token + type: array + items: + $ref: '#/definitions/Project' + headers: + X-Next-Page: + type: integer + description: The index of the next page + X-Page: + type: integer + description: The index of the current page (starting at 1) + X-Per-Page: + type: integer + description: The number of items to list per page + X-Prev-Page: + type: integer + description: The index of the previous page + X-Total: + type: integer + description: The total number of items + X-Total-Pages: + type: integer + description: The total number of pages + Link: + type: string + description: Pagination link header. + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: - - $ref: '#/parameters/offsetParam' - - $ref: '#/parameters/limitParam' - - name: filter - required: true - type: string - in: query - description: | - Url encoded list of Supported filters - - id - - status - - type - - memberOnly - - keyword - - name - - code - - customer - - manager + - $ref: '#/parameters/pageParam' + - $ref: '#/parameters/perPageParam' + - $ref: '#/parameters/idQueryParam' + - $ref: '#/parameters/statusQueryParam' + - $ref: '#/parameters/typeQueryParam' + - $ref: '#/parameters/memberOnlyQueryParam' + - $ref: '#/parameters/keywordQueryParam' + - $ref: '#/parameters/nameQueryParam' + - $ref: '#/parameters/codeQueryParam' + - $ref: '#/parameters/customerQueryParam' + - $ref: '#/parameters/managerQueryParam' - name: sort required: false description: > @@ -120,18 +173,22 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewProjectBodyParam' + $ref: '#/definitions/NewProject' responses: - '201': + '200': description: Returns the newly created project schema: - $ref: '#/definitions/ProjectResponse' - '403': - description: No permission or wrong token + $ref: '#/definitions/Project' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}': @@ -145,15 +202,23 @@ paths: '200': description: a project schema: - $ref: '#/definitions/ProjectResponse' - '403': - description: No permission or wrong token + $ref: '#/definitions/Project' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' parameters: - $ref: '#/parameters/projectIdParam' - name: fields @@ -184,21 +249,25 @@ paths: Successfully updated project. Returns original and updated project object schema: - $ref: '#/definitions/UpdateProjectResponse' + $ref: '#/definitions/Project' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -210,7 +279,7 @@ paths: Only specify those properties that needs to be updated. `cancelReason` is mandatory if status is cancelled schema: - $ref: '#/definitions/ProjectBodyParam' + $ref: '#/definitions/NewProject' delete: tags: - project @@ -222,18 +291,30 @@ paths: responses: '204': description: Project successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If project is not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' '/projects/{projectId}/attachments': post: tags: - - project + - project attachments description: add a new project attachment security: - Bearer: [] @@ -243,24 +324,32 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewProjectAttachmentBodyParam' + $ref: '#/definitions/NewProjectAttachment' responses: - '201': + '200': description: Returns the newly created project attachment schema: - $ref: '#/definitions/NewProjectAttachmentResponse' + $ref: '#/definitions/ProjectAttachment' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/attachments/{id}': patch: tags: - - project + - project attachments description: Update an existing attachment security: - Bearer: [] @@ -276,27 +365,35 @@ paths: required: true description: Specify only those properties that needs to be updated schema: - $ref: '#/definitions/NewProjectAttachmentBodyParam' + $ref: '#/definitions/NewProjectAttachment' responses: - '201': + '200': description: Returns the newly created project schema: - $ref: '#/definitions/NewProjectAttachmentResponse' + $ref: '#/definitions/ProjectAttachment' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If project attachment is not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' delete: tags: - - project + - project attachments description: remove an existing attachment security: - Bearer: [] @@ -310,18 +407,30 @@ paths: responses: '204': description: Attachment successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If attachment is not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' '/projects/{projectId}/members': post: tags: - - project + - project members description: add a new project member security: - Bearer: [] @@ -331,24 +440,32 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewProjectMemberBodyParam' + $ref: '#/definitions/NewProjectMember' responses: - '201': + '200': description: Returns the newly created project schema: - $ref: '#/definitions/NewProjectMemberResponse' + $ref: '#/definitions/ProjectMember' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/members/{id}': delete: tags: - - project + - project members description: Delete a project member security: - Bearer: [] @@ -361,13 +478,29 @@ paths: responses: '204': description: Member successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If Project doesn't contain such Member + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' patch: tags: - - project + - project members security: - Bearer: [] description: Support editing project member roles & primary option. @@ -377,21 +510,25 @@ paths: Successfully updated project member. Returns entire project member object schema: - $ref: '#/definitions/UpdateProjectMemberResponse' + $ref: '#/definitions/ProjectMember' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -404,7 +541,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/UpdateProjectMemberBodyParam' + $ref: '#/definitions/UpdateProjectMember' '/projects/{projectId}/phases/db': parameters: - $ref: '#/parameters/projectIdParam' @@ -435,9 +572,23 @@ paths: '200': description: A list of project phases schema: - $ref: '#/definitions/ProjectPhaseListResponse' + type: array + items: + $ref: '#/definitions/ProjectPhase' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/phases': @@ -470,9 +621,23 @@ paths: '200': description: A list of project phases schema: - $ref: '#/definitions/ProjectPhaseListResponse' + type: array + items: + $ref: '#/definitions/ProjectPhase' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -492,28 +657,33 @@ paths: schema: type: object allOf: - - $ref: '#/definitions/ProjectPhaseBodyParam' + - $ref: '#/definitions/ProjectPhaseRequest' properties: - param: - type: object - properties: - productTemplateId: - type: number - format: long - description: >- - the optional productTemplateId used to populate a new - phase product for the created phase + productTemplateId: + type: number + format: long + description: >- + the optional productTemplateId used to populate a new + phase product for the created phase responses: - '201': + '200': description: Returns the newly created project phase schema: - $ref: '#/definitions/ProjectPhaseResponse' + $ref: '#/definitions/ProjectPhase' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/phases/{phaseId}': @@ -532,15 +702,27 @@ paths: '200': description: a project phase schema: - $ref: '#/definitions/ProjectPhaseResponse' + $ref: '#/definitions/ProjectPhase' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' parameters: - $ref: '#/parameters/phaseIdParam' operationId: getProjectPhase @@ -559,21 +741,25 @@ paths: '200': description: Successfully updated project phase. schema: - $ref: '#/definitions/ProjectPhaseResponse' + $ref: '#/definitions/ProjectPhase' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -582,7 +768,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/ProjectPhaseBodyParam' + $ref: '#/definitions/ProjectPhaseRequest' delete: tags: - phase @@ -596,14 +782,26 @@ paths: responses: '204': description: Project phase successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If project is not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' '/projects/{projectId}/phases/{phaseId}/products/db': parameters: - $ref: '#/parameters/projectIdParam' @@ -621,9 +819,23 @@ paths: '200': description: A list of phase products schema: - $ref: '#/definitions/PhaseProductListResponse' + type: array + items: + $ref: '#/definitions/PhaseProduct' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/phases/{phaseId}/products': @@ -643,9 +855,23 @@ paths: '200': description: A list of phase products schema: - $ref: '#/definitions/PhaseProductListResponse' + type: array + items: + $ref: '#/definitions/PhaseProduct' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -660,18 +886,26 @@ paths: name: body required: true schema: - $ref: '#/definitions/PhaseProductBodyParam' + $ref: '#/definitions/PhaseProductRequest' responses: - '201': + '200': description: Returns the newly created phase product schema: - $ref: '#/definitions/PhaseProductResponse' + $ref: '#/definitions/PhaseProduct' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/phases/{phaseId}/products/{productId}': @@ -691,15 +925,27 @@ paths: '200': description: a phase product schema: - $ref: '#/definitions/PhaseProductResponse' + $ref: '#/definitions/PhaseProduct' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' parameters: - $ref: '#/parameters/phaseIdParam' operationId: getPhaseProduct @@ -716,21 +962,25 @@ paths: '200': description: Successfully updated phase product. schema: - $ref: '#/definitions/PhaseProductResponse' + $ref: '#/definitions/PhaseProduct' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -739,7 +989,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/PhaseProductBodyParam' + $ref: '#/definitions/PhaseProductRequest' delete: tags: - phase product @@ -753,14 +1003,26 @@ paths: responses: '204': description: Project phase successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If project is not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' '/projects/{projectId}/upgrade': post: tags: @@ -778,18 +1040,22 @@ paths: required: true description: Project upgrade body schema: - $ref: '#/definitions/ProjectUpgradeBodyParam' + $ref: '#/definitions/ProjectUpgrade' responses: '200': description: Project migrated successfully schema: - $ref: '#/definitions/ProjectUpgradeResponse' + $ref: '#/definitions/ProjectUpgrade' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '400': - description: Invalid input + description: Bad request schema: $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': @@ -797,7 +1063,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' /projects/metadata: @@ -816,8 +1082,12 @@ paths: description: The metadata schema: $ref: '#/definitions/AllMetadataResponse' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' /projects/metadata/projectTemplates: @@ -832,9 +1102,15 @@ paths: '200': description: A list of project templates schema: - $ref: '#/definitions/ProjectTemplateListResponse' - '403': - description: No permission or wrong token + type: array + items: + $ref: '#/definitions/ProjectTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -849,18 +1125,26 @@ paths: name: body required: true schema: - $ref: '#/definitions/ProjectTemplateBodyParam' + $ref: '#/definitions/ProjectTemplateRequest' responses: - '201': + '200': description: Returns the newly created project template schema: - $ref: '#/definitions/ProjectTemplateResponse' + $ref: '#/definitions/ProjectTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/projectTemplates/{templateId}': @@ -876,15 +1160,23 @@ paths: '200': description: a project template schema: - $ref: '#/definitions/ProjectTemplateResponse' - '403': - description: No permission or wrong token + $ref: '#/definitions/ProjectTemplate' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' parameters: - $ref: '#/parameters/templateIdParam' operationId: getProjectTemplate @@ -903,21 +1195,25 @@ paths: '200': description: Successfully updated project template. schema: - $ref: '#/definitions/ProjectTemplateResponse' + $ref: '#/definitions/ProjectTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -926,7 +1222,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/ProjectTemplateBodyParam' + $ref: '#/definitions/ProjectTemplateRequest' delete: tags: - projectTemplate @@ -940,14 +1236,26 @@ paths: responses: '204': description: Project template successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If project is not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' '/projects/metadata/projectTemplates/{templateId}/upgrade': post: tags: @@ -962,24 +1270,28 @@ paths: name: body required: true schema: - $ref: '#/definitions/ProjectTemplateUpgradeBodyParam' + $ref: '#/definitions/ProjectTemplateUpgradeBody' responses: '200': description: Project template successfully upgrade + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If project template is not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' '500': - description: Server Error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' /projects/metadata/productTemplates: @@ -994,9 +1306,15 @@ paths: '200': description: A list of product templates schema: - $ref: '#/definitions/ProductTemplateListResponse' - '403': - description: No permission or wrong token + type: array + items: + $ref: '#/definitions/ProductTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -1011,18 +1329,26 @@ paths: name: body required: true schema: - $ref: '#/definitions/ProductTemplateBodyParam' + $ref: '#/definitions/ProductTemplateRequest' responses: - '201': + '200': description: Returns the newly created product template schema: - $ref: '#/definitions/ProductTemplateResponse' + $ref: '#/definitions/ProductTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/productTemplates/{templateId}': @@ -1038,15 +1364,23 @@ paths: '200': description: a product template schema: - $ref: '#/definitions/ProductTemplateResponse' - '403': - description: No permission or wrong token + $ref: '#/definitions/ProductTemplate' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' parameters: - $ref: '#/parameters/templateIdParam' operationId: getProductTemplate @@ -1065,21 +1399,25 @@ paths: '200': description: Successfully updated product template. schema: - $ref: '#/definitions/ProductTemplateResponse' + $ref: '#/definitions/ProductTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -1088,7 +1426,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/ProductTemplateBodyParam' + $ref: '#/definitions/ProductTemplateRequest' delete: tags: - productTemplate @@ -1102,14 +1440,26 @@ paths: responses: '204': description: Product template successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If product is not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' '/projects/metadata/productTemplates/{templateId}/upgrade': post: tags: @@ -1124,24 +1474,28 @@ paths: name: body required: true schema: - $ref: '#/definitions/ProductTemplateUpgradeBodyParam' + $ref: '#/definitions/ProductTemplateUpgradeBody' responses: '200': description: Product template successfully upgraded + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If product template is not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' '500': - description: Server Error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' /projects/metadata/productCategories: @@ -1158,9 +1512,15 @@ paths: '200': description: A list of product categories schema: - $ref: '#/definitions/ProductCategoryListResponse' - '403': - description: No permission or wrong token + type: array + items: + $ref: '#/definitions/ProductCategory' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -1177,18 +1537,26 @@ paths: name: body required: true schema: - $ref: '#/definitions/ProductCategoryCreateBodyParam' + $ref: '#/definitions/ProductCategoryCreateRequest' responses: - '201': + '200': description: Returns the newly created product category schema: - $ref: '#/definitions/ProductCategoryResponse' + $ref: '#/definitions/ProductCategory' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/productCategories/{key}': @@ -1204,15 +1572,23 @@ paths: '200': description: a product category schema: - $ref: '#/definitions/ProductCategoryResponse' - '403': - description: No permission or wrong token + $ref: '#/definitions/ProductCategory' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' parameters: - $ref: '#/parameters/keyParam' operationId: getProductCategory @@ -1229,21 +1605,25 @@ paths: '200': description: Successfully updated product category. schema: - $ref: '#/definitions/ProductCategoryResponse' + $ref: '#/definitions/ProductCategory' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -1252,7 +1632,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/ProductCategoryBodyParam' + $ref: '#/definitions/ProductCategoryRequest' delete: tags: - productCategory @@ -1266,14 +1646,26 @@ paths: responses: '204': description: Product category successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If product category is not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' /projects/metadata/projectTypes: get: tags: @@ -1286,9 +1678,15 @@ paths: '200': description: A list of project types schema: - $ref: '#/definitions/ProjectTypeListResponse' - '403': - description: No permission or wrong token + type: array + items: + $ref: '#/definitions/ProjectType' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -1305,18 +1703,26 @@ paths: name: body required: true schema: - $ref: '#/definitions/ProjectTypeCreateBodyParam' + $ref: '#/definitions/ProjectTypeCreateRequest' responses: - '201': + '200': description: Returns the newly created project type schema: - $ref: '#/definitions/ProjectTypeResponse' + $ref: '#/definitions/ProjectType' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/projectTypes/{key}': @@ -1330,15 +1736,23 @@ paths: '200': description: a project type schema: - $ref: '#/definitions/ProjectTypeResponse' - '403': - description: No permission or wrong token + $ref: '#/definitions/ProjectType' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' parameters: - $ref: '#/parameters/keyParam' operationId: getProjectType @@ -1355,21 +1769,25 @@ paths: '200': description: Successfully updated project type. schema: - $ref: '#/definitions/ProjectTypeResponse' + $ref: '#/definitions/ProjectType' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -1378,7 +1796,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/ProjectTypeBodyParam' + $ref: '#/definitions/ProjectTypeRequest' delete: tags: - projectType @@ -1392,14 +1810,26 @@ paths: responses: '204': description: Project type successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If project is not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' /projects/metadata/orgConfig: get: tags: @@ -1411,21 +1841,33 @@ paths: Retrieve all organization configs. All user roles can access this endpoint. parameters: - - name: filter + - name: orgId required: true type: string in: query - description: | - Url encoded list of Supported filters - - orgId (required) - - configName + description: organization id + - name: configName + required: false + type: string + in: query + description: configuration name responses: '200': description: A list of organization configs schema: - $ref: '#/definitions/OrgConfigListResponse' - '403': - description: No permission or wrong token + type: array + items: + $ref: '#/definitions/OrgConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -1442,18 +1884,26 @@ paths: name: body required: true schema: - $ref: '#/definitions/OrgConfigCreateBodyParam' + $ref: '#/definitions/OrgConfigCreateRequest' responses: - '201': + '200': description: Returns the newly created organization config schema: - $ref: '#/definitions/OrgConfigResponse' + $ref: '#/definitions/OrgConfig' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/orgConfig/{id}': @@ -1467,15 +1917,23 @@ paths: '200': description: a project type schema: - $ref: '#/definitions/OrgConfigResponse' - '403': - description: No permission or wrong token + $ref: '#/definitions/OrgConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' parameters: - $ref: '#/parameters/idParam' operationId: getOrgConfig @@ -1492,21 +1950,25 @@ paths: '200': description: Successfully updated organization config. schema: - $ref: '#/definitions/OrgConfigResponse' + $ref: '#/definitions/OrgConfig' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -1515,7 +1977,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/OrgConfigCreateBodyParam' + $ref: '#/definitions/OrgConfigCreateRequest' delete: tags: - orgConfig @@ -1529,14 +1991,26 @@ paths: responses: '204': description: Organization config successfully removed + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: If organization config is not found schema: $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' /timelines: get: tags: @@ -1546,25 +2020,37 @@ paths: - Bearer: [] description: Retrieve timelines which its projects are accessible by the user. parameters: - - name: filter + - name: reference required: false type: string in: query - description: | - Url encoded list of supported filters - - reference - - referenceId + description: the reference filter + - name: referenceId + required: false + type: string + in: query + description: the reference id filter responses: '200': description: A list of timelines schema: - $ref: '#/definitions/TimelineListResponse' + type: array + items: + $ref: '#/definitions/Timeline' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -1581,18 +2067,26 @@ paths: name: body required: true schema: - $ref: '#/definitions/TimelineBodyParam' + $ref: '#/definitions/TimelineRequest' responses: - '201': + '200': description: Returns the newly created timeline schema: - $ref: '#/definitions/TimelineResponse' + $ref: '#/definitions/Timeline' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/timelines/{timelineId}': @@ -1608,17 +2102,25 @@ paths: '200': description: a timeline schema: - $ref: '#/definitions/TimelineResponse' + $ref: '#/definitions/Timeline' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -1637,21 +2139,25 @@ paths: '200': description: Successfully updated timeline. schema: - $ref: '#/definitions/TimelineResponse' + $ref: '#/definitions/Timeline' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -1660,7 +2166,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/TimelineBodyParam' + $ref: '#/definitions/TimelineRequest' delete: tags: - timeline @@ -1674,16 +2180,24 @@ paths: responses: '204': description: Timeline successfully removed + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/timelines/{timelineId}/milestones': @@ -1708,13 +2222,23 @@ paths: '200': description: A list of milestones schema: - $ref: '#/definitions/MilestoneListResponse' + type: array + items: + $ref: '#/definitions/Milestone' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -1733,18 +2257,26 @@ paths: name: body required: true schema: - $ref: '#/definitions/MilestonePostBodyParam' + $ref: '#/definitions/MilestonePostRequest' responses: - '201': + '200': description: Returns the newly created milestone schema: - $ref: '#/definitions/MilestoneResponse' + $ref: '#/definitions/Milestone' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/timelines/{timelineId}/milestones/{milestoneId}': @@ -1763,17 +2295,25 @@ paths: '200': description: a milestone schema: - $ref: '#/definitions/MilestoneResponse' + $ref: '#/definitions/Milestone' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' operationId: getMilestone @@ -1792,21 +2332,25 @@ paths: '200': description: Successfully updated milestone. schema: - $ref: '#/definitions/MilestoneResponse' + $ref: '#/definitions/Milestone' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -1814,7 +2358,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/MilestonePatchBodyParam' + $ref: '#/definitions/MilestonePatchRequest' delete: tags: - milestone @@ -1826,16 +2370,24 @@ paths: responses: '204': description: Milestone successfully removed + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' /timelines/metadata/milestoneTemplates: @@ -1854,25 +2406,33 @@ paths: description: sort by `order`. Default is `order asc` in: query type: string - - name: filter + - name: reference required: false + description: the reference filter + in: query type: string + - name: referenceId + required: false + description: the reference id filter in: query - description: | - Url encoded list of supported filters - - reference - - referenceId + type: string responses: '200': description: A list of milestone templates schema: - $ref: '#/definitions/MilestoneTemplateListResponse' - '403': - description: No permission or wrong token + type: array + items: + $ref: '#/definitions/MilestoneTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -1891,18 +2451,26 @@ paths: name: body required: true schema: - $ref: '#/definitions/MilestoneTemplateBodyParam' + $ref: '#/definitions/MilestoneTemplate' responses: - '201': + '200': description: Returns the newly created milestone template schema: - $ref: '#/definitions/MilestoneTemplateResponse' - '403': - description: No permission or wrong token + $ref: '#/definitions/MilestoneTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' /timelines/metadata/milestoneTemplates/clone: @@ -1922,20 +2490,30 @@ paths: schema: $ref: '#/definitions/MilestoneCloneTemplateRequest' responses: - '201': + '200': description: Returns the list of cloned milestone templates schema: - $ref: '#/definitions/MilestoneTemplateListResponse' + type: array + items: + $ref: '#/definitions/MilestoneTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/timelines/metadata/milestoneTemplates/{milestoneTemplateId}': @@ -1953,17 +2531,21 @@ paths: '200': description: a milestone template schema: - $ref: '#/definitions/MilestoneTemplateResponse' - '403': - description: No permission or wrong token + $ref: '#/definitions/MilestoneTemplate' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' operationId: getMilestoneTemplate @@ -1980,21 +2562,25 @@ paths: '200': description: Successfully updated milestone template. schema: - $ref: '#/definitions/MilestoneTemplateResponse' + $ref: '#/definitions/MilestoneTemplate' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' - default: - description: error payload + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' parameters: @@ -2002,7 +2588,7 @@ paths: in: body required: true schema: - $ref: '#/definitions/MilestoneTemplateBodyParam' + $ref: '#/definitions/MilestoneTemplate' delete: tags: - milestoneTemplates @@ -2014,16 +2600,24 @@ paths: responses: '204': description: Milestone template successfully removed + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '404': description: Not found schema: $ref: '#/definitions/ErrorModel' - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/members/invite': @@ -2040,13 +2634,13 @@ paths: '200': description: The invite for current user schema: - $ref: '#/definitions/ProjectMemberInviteResponse' + $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure' '400': - description: Invalid input + description: Bad request schema: $ref: '#/definitions/ErrorModel' - '403': - description: No permission or wrong token + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2054,7 +2648,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -2074,20 +2668,24 @@ paths: schema: $ref: '#/definitions/AddProjectMemberInvitesRequest' responses: - '201': + '200': description: Returns the newly created invite schema: - $ref: '#/definitions/ProjectMemberInviteResponse' + $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure' '400': - description: Invalid input + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' put: @@ -2110,17 +2708,21 @@ paths: '200': description: Returns the newly updated invite schema: - $ref: '#/definitions/ProjectMemberInviteResponse' + $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure' '400': - description: Invalid input + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '403': - description: No permission or wrong token + description: Forbidden schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' @@ -2137,9 +2739,13 @@ paths: '200': description: The model for the latest revision of latest version schema: - $ref: '#/definitions/FormResponse' - '422': - description: Invalid input + $ref: '#/definitions/Form' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2147,7 +2753,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/form/{key}/versions': @@ -2163,9 +2769,15 @@ paths: '200': description: The model list for the all version schema: - $ref: '#/definitions/FormListResponse' - '422': - description: Invalid input + type: array + items: + $ref: '#/definitions/Form' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' '404': @@ -2173,7 +2785,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -2188,14 +2800,18 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewFormParam' + $ref: '#/definitions/NewForm' responses: - '201': + '200': description: The model created schema: - $ref: '#/definitions/FormResponse' - '422': - description: Invalid input + $ref: '#/definitions/Form' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2203,7 +2819,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/form/{key}/versions/{version}': @@ -2220,9 +2836,13 @@ paths: '200': description: The model for the particular version schema: - $ref: '#/definitions/FormResponse' - '422': - description: Invalid input + $ref: '#/definitions/Form' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2230,7 +2850,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' patch: @@ -2246,14 +2866,18 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewFormParam' + $ref: '#/definitions/NewForm' responses: - '201': + '200': description: The model updated schema: - $ref: '#/definitions/FormResponse' - '422': - description: Invalid input + $ref: '#/definitions/Form' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' '404': @@ -2261,7 +2885,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' delete: @@ -2276,8 +2900,12 @@ paths: responses: '204': description: Delete succuessful - '422': - description: Invalid input + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '400': + description: Bad request schema: $ref: '#/definitions/ErrorModel' '404': @@ -2285,7 +2913,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/form/{key}/versions/{version}/revisions': @@ -2302,9 +2930,15 @@ paths: '200': description: The model for the particular version schema: - $ref: '#/definitions/FormListResponse' - '422': - description: Invalid input + type: array + items: + $ref: '#/definitions/Form' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2312,7 +2946,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -2328,14 +2962,18 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewFormParam' + $ref: '#/definitions/NewForm' responses: - '201': + '200': description: The model created schema: - $ref: '#/definitions/FormResponse' - '422': - description: Invalid input + $ref: '#/definitions/Form' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2343,7 +2981,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/form/{key}/versions/{version}/revisions/{revision}': @@ -2361,9 +2999,13 @@ paths: '200': description: The model for the particular version schema: - $ref: '#/definitions/FormResponse' - '422': - description: Invalid input + $ref: '#/definitions/Form' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2371,7 +3013,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' delete: @@ -2387,8 +3029,12 @@ paths: responses: '204': description: Delete succuessful - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2396,7 +3042,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' @@ -2413,9 +3059,13 @@ paths: '200': description: The model for the latest revision of latest version schema: - $ref: '#/definitions/PriceConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PriceConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2423,7 +3073,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/priceConfig/{key}/versions': @@ -2439,9 +3089,15 @@ paths: '200': description: The model list for the all version schema: - $ref: '#/definitions/PriceConfigListResponse' - '422': - description: Invalid input + type: array + items: + $ref: '#/definitions/PriceConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2449,7 +3105,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -2464,18 +3120,22 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewPriceConfigParam' + $ref: '#/definitions/NewPriceConfig' responses: - '201': + '200': description: The model created schema: - $ref: '#/definitions/PriceConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PriceConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/priceConfig/{key}/versions/{version}': @@ -2492,9 +3152,13 @@ paths: '200': description: The model for the particular version schema: - $ref: '#/definitions/PriceConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PriceConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2502,7 +3166,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' patch: @@ -2518,14 +3182,18 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewPriceConfigParam' + $ref: '#/definitions/NewPriceConfig' responses: - '201': + '200': description: The model updated schema: - $ref: '#/definitions/PriceConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PriceConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2533,7 +3201,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' delete: @@ -2548,8 +3216,12 @@ paths: responses: '204': description: Delete succuessful - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2557,7 +3229,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/priceConfig/{key}/versions/{version}/revisions': @@ -2574,9 +3246,15 @@ paths: '200': description: The model for the particular version schema: - $ref: '#/definitions/PriceConfigListResponse' - '422': - description: Invalid input + type: array + items: + $ref: '#/definitions/PriceConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2584,7 +3262,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -2600,14 +3278,18 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewPriceConfigParam' + $ref: '#/definitions/NewPriceConfig' responses: - '201': + '200': description: The model created schema: - $ref: '#/definitions/PriceConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PriceConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2615,7 +3297,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/priceConfig/{key}/versions/{version}/revisions/{revision}': @@ -2633,9 +3315,13 @@ paths: '200': description: The model for the particular version schema: - $ref: '#/definitions/PriceConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PriceConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2643,7 +3329,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' delete: @@ -2659,8 +3345,12 @@ paths: responses: '204': description: Delete succuessful - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2668,7 +3358,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' @@ -2685,9 +3375,13 @@ paths: '200': description: The model for the latest revision of latest version schema: - $ref: '#/definitions/PlanConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PlanConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2695,7 +3389,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/planConfig/{key}/versions': @@ -2711,9 +3405,15 @@ paths: '200': description: The model list for the all version schema: - $ref: '#/definitions/PlanConfigListResponse' - '422': - description: Invalid input + type: array + items: + $ref: '#/definitions/PlanConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2721,7 +3421,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -2736,18 +3436,22 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewPlanConfigParam' + $ref: '#/definitions/NewPlanConfig' responses: - '201': + '200': description: The model created schema: - $ref: '#/definitions/PlanConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PlanConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/planConfig/{key}/versions/{version}': @@ -2764,9 +3468,13 @@ paths: '200': description: The model for the particular version schema: - $ref: '#/definitions/PlanConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PlanConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2774,7 +3482,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' patch: @@ -2790,14 +3498,18 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewPlanConfigParam' + $ref: '#/definitions/NewPlanConfig' responses: - '201': + '200': description: The model updated schema: - $ref: '#/definitions/PlanConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PlanConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2805,7 +3517,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' delete: @@ -2820,8 +3532,12 @@ paths: responses: '204': description: Delete succuessful - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2829,7 +3545,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/planConfig/{key}/versions/{version}/revisions': @@ -2846,9 +3562,15 @@ paths: '200': description: The model for the particular version schema: - $ref: '#/definitions/PlanConfigListResponse' - '422': - description: Invalid input + type: array + items: + $ref: '#/definitions/PlanConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2856,7 +3578,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' post: @@ -2872,14 +3594,18 @@ paths: name: body required: true schema: - $ref: '#/definitions/NewPlanConfigParam' + $ref: '#/definitions/NewPlanConfig' responses: - '201': + '200': description: The model created schema: - $ref: '#/definitions/PlanConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PlanConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2887,7 +3613,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' '/projects/metadata/planConfig/{key}/versions/{version}/revisions/{revision}': @@ -2905,9 +3631,13 @@ paths: '200': description: The model for the particular version schema: - $ref: '#/definitions/PlanConfigResponse' - '422': - description: Invalid input + $ref: '#/definitions/PlanConfig' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2915,7 +3645,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' delete: @@ -2931,8 +3661,12 @@ paths: responses: '204': description: Delete succuessful - '422': - description: Invalid input + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized schema: $ref: '#/definitions/ErrorModel' '404': @@ -2940,7 +3674,7 @@ paths: schema: $ref: '#/definitions/ErrorModel' '500': - description: Invalid server state or unknown error + description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' @@ -3021,20 +3755,21 @@ parameters: required: true type: integer format: int64 - offsetParam: - name: offset - description: number of items to skip. Defaults to 0 + pageParam: + name: page in: query + description: The page number. required: false type: integer - format: int32 - limitParam: - name: limit - description: max records to return. Defaults to 20 + default: 1 + perPageParam: + name: perPage in: query + description: The number of items to list per page. required: false type: integer - format: int32 + default: 20 + maximum: 100 modelKeyParam: name: key in: path @@ -3069,6 +3804,60 @@ parameters: required: true type: integer format: int64 + idQueryParam: + name: id + in: query + description: id filter + required: false + type: string + statusQueryParam: + name: status + in: query + description: status filter + required: false + type: string + typeQueryParam: + name: type + in: query + description: type filter + required: false + type: string + memberOnlyQueryParam: + name: memberOnly + in: query + description: memberOnly filter + required: false + type: boolean + keywordQueryParam: + name: keyword + in: query + description: keyword filter + required: false + type: string + nameQueryParam: + name: name + in: query + description: name filter + required: false + type: string + codeQueryParam: + name: code + in: query + description: code filter + required: false + type: string + customerQueryParam: + name: customer + in: query + description: customer filter + required: false + type: string + managerQueryParam: + name: manager + in: query + description: manager filter + required: false + type: string definitions: ResponseMetadata: title: Metadata object for a response @@ -3081,24 +3870,8 @@ definitions: ErrorModel: type: object properties: - id: + message: type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - description: http status code - type: integer - format: int32 - debug: - type: object - content: - type: object ProjectBookMark: title: Project bookmark type: object @@ -3107,16 +3880,6 @@ definitions: type: string address: type: string - ProjectBodyParam: - type: object - properties: - param: - $ref: '#/definitions/Project' - ProjectUpgradeBodyParam: - type: object - properties: - param: - $ref: '#/definitions/ProjectUpgrade' NewProject: type: object required: @@ -3215,11 +3978,6 @@ definitions: description: the project template identifier type: number format: long - NewProjectBodyParam: - type: object - properties: - param: - $ref: '#/definitions/NewProject' ChallengeEligibility: description: Object describing who is eligible to work on this task type: object @@ -3431,11 +4189,6 @@ definitions: - customer - manager - copilot - NewProjectMemberBodyParam: - type: object - properties: - param: - $ref: '#/definitions/NewProjectMember' UpdateProjectMember: title: Project Member object type: object @@ -3452,11 +4205,6 @@ definitions: - customer - manager - copilot - UpdateProjectMemberBodyParam: - type: object - properties: - param: - $ref: '#/definitions/UpdateProjectMember' NewProjectAttachment: title: Project attachment request type: object @@ -3494,30 +4242,6 @@ definitions: type: integer format: int64 description: Users allowed to access the attachment - NewProjectAttachmentBodyParam: - type: object - properties: - param: - $ref: '#/definitions/NewProjectAttachment' - NewProjectAttachmentResponse: - title: Project attachment object response - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - $ref: '#/definitions/ProjectAttachment' ProjectAttachment: title: Project attachment type: object @@ -3605,129 +4329,24 @@ definitions: format: int64 description: READ-ONLY. User that last updated this task readOnly: true - NewProjectMemberResponse: - title: Project member object response + ProjectTemplateRequest: + title: Project template request object type: object + required: + - name + - key + - category properties: - id: + name: type: string - description: unique id identifying the request - version: + description: the project template name + key: + type: string + description: the project template key + category: type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - $ref: '#/definitions/ProjectMember' - UpdateProjectMemberResponse: - title: Project member object response - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - $ref: '#/definitions/ProjectMember' - ProjectResponse: - title: Single project object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - $ref: '#/definitions/Project' - UpdateProjectResponse: - title: response with original and updated project object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - type: object - properties: - original: - $ref: '#/definitions/Project' - updated: - $ref: '#/definitions/Project' - ProjectListResponse: - title: List response - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/Project' - ProjectTemplateRequest: - title: Project template request object - type: object - required: - - name - - key - - category - properties: - name: - type: string - description: the project template name - key: - type: string - description: the project template key - category: - type: string - description: the project template category - scope: + description: the project template category + scope: type: object description: the project template scope phases: @@ -3739,28 +4358,22 @@ definitions: $ref: '#/definitions/VersionModelParam' planConfig: $ref: '#/definitions/VersionModelParam' - ProjectTemplateUpgradeBodyParam: + ProjectTemplateUpgradeBody: title: Project template type: object properties: - param: - type: object - properties: - form: - $ref: '#/definitions/VersionModelParam' - priceConfig: - $ref: '#/definitions/VersionModelParam' - planConfig: - $ref: '#/definitions/VersionModelParam' - ProductTemplateUpgradeBodyParam: + form: + $ref: '#/definitions/VersionModelParam' + priceConfig: + $ref: '#/definitions/VersionModelParam' + planConfig: + $ref: '#/definitions/VersionModelParam' + ProductTemplateUpgradeBody: title: Product template type: object properties: - param: - type: object - properties: - form: - $ref: '#/definitions/VersionModelParam' + form: + $ref: '#/definitions/VersionModelParam' VersionModelParam: title: version model param type: object @@ -3771,14 +4384,6 @@ definitions: version: type: number description: the version for model - ProjectTemplateBodyParam: - title: Project template body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/ProjectTemplateRequest' ProjectTemplate: title: Project template object allOf: @@ -3813,51 +4418,6 @@ definitions: description: READ-ONLY. User that last updated this object readOnly: true - $ref: '#/definitions/ProjectTemplateRequest' - ProjectTemplateResponse: - title: Single project template response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/ProjectTemplate' - ProjectTemplateListResponse: - title: Project template list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/ProjectTemplate' ProductTemplateRequest: title: Product template request object type: object @@ -3900,14 +4460,6 @@ definitions: isAddOn: type: boolean description: the flag that shows if the product template is an add on - ProductTemplateBodyParam: - title: Product template body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/ProductTemplateRequest' ProductTemplate: title: Product template object allOf: @@ -3946,70 +4498,6 @@ definitions: type: string description: The product category of the product template - $ref: '#/definitions/ProductTemplateRequest' - ProjectUpgradeResponse: - title: Project upgrade response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - ProductTemplateResponse: - title: Single product template response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/ProductTemplate' - ProductTemplateListResponse: - title: Product template list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/ProductTemplate' ProjectPhaseRequest: title: Project phase request object type: object @@ -4046,14 +4534,6 @@ definitions: type: number format: integer description: the project phase order - ProjectPhaseBodyParam: - title: Project phase body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/ProjectPhaseRequest' ProjectPhase: title: Project phase object allOf: @@ -4088,51 +4568,6 @@ definitions: description: READ-ONLY. User that last updated this object readOnly: true - $ref: '#/definitions/ProjectPhaseRequest' - ProjectPhaseResponse: - title: Single project phase response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/ProjectPhase' - ProjectPhaseListResponse: - title: Project phase list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/ProjectPhase' PhaseProductRequest: title: Phase product request object type: object @@ -4161,14 +4596,6 @@ definitions: details: type: object description: the phase product details - PhaseProductBodyParam: - title: Phase product body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/PhaseProductRequest' PhaseProduct: title: Phase product object allOf: @@ -4203,51 +4630,6 @@ definitions: description: READ-ONLY. User that last updated this object readOnly: true - $ref: '#/definitions/PhaseProductRequest' - PhaseProductResponse: - title: Single phase product response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/PhaseProduct' - PhaseProductListResponse: - title: Phase product list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/PhaseProduct' ProductCategoryRequest: title: Product category request object type: object @@ -4257,14 +4639,6 @@ definitions: displayName: type: string description: the product category display name - ProductCategoryBodyParam: - title: Product category body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/ProductCategoryRequest' ProductCategoryCreateRequest: title: Product category creation request object type: object @@ -4277,14 +4651,6 @@ definitions: type: string description: the product category key - $ref: '#/definitions/ProductCategoryRequest' - ProductCategoryCreateBodyParam: - title: Product category creation body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/ProductCategoryCreateRequest' ProductCategory: title: Product category object allOf: @@ -4317,51 +4683,6 @@ definitions: description: READ-ONLY. User that last updated this object readOnly: true - $ref: '#/definitions/ProductCategoryCreateRequest' - ProductCategoryResponse: - title: Single product category response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/ProductCategory' - ProductCategoryListResponse: - title: Product category list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/ProductCategory' ProjectTypeRequest: title: Project type request object type: object @@ -4371,14 +4692,6 @@ definitions: displayName: type: string description: the project type display name - ProjectTypeBodyParam: - title: Project type body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/ProjectTypeRequest' ProjectTypeCreateRequest: title: Project type creation request object type: object @@ -4391,14 +4704,6 @@ definitions: type: string description: the project type key - $ref: '#/definitions/ProjectTypeRequest' - ProjectTypeCreateBodyParam: - title: Project type creation body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/ProjectTypeCreateRequest' ProjectType: title: Project type object allOf: @@ -4454,14 +4759,6 @@ definitions: type: string description: the organization config id - $ref: '#/definitions/OrgConfigRequest' - OrgConfigCreateBodyParam: - title: Organization config creation body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/OrgConfigCreateRequest' OrgConfig: title: Organization config object allOf: @@ -4494,109 +4791,19 @@ definitions: readOnly: true createdBy: type: integer - format: int64 - description: READ-ONLY. User who created this object - readOnly: true - updatedAt: - type: string - description: READ-ONLY. Datetime (GMT) when object was updated - readOnly: true - updatedBy: - type: integer - format: int64 - description: READ-ONLY. User that last updated this object - readOnly: true - - $ref: '#/definitions/OrgConfigCreateRequest' - OrgConfigResponse: - title: Single organization config response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/OrgConfig' - OrgConfigListResponse: - title: Organization confige list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/OrgConfig' - ProjectTypeResponse: - title: Single project type response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/ProjectType' - ProjectTypeListResponse: - title: Project type list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: + format: int64 + description: READ-ONLY. User who created this object + readOnly: true + updatedAt: type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/ProjectType' + description: READ-ONLY. Datetime (GMT) when object was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this object + readOnly: true + - $ref: '#/definitions/OrgConfigCreateRequest' TimelineRequest: title: Timeline request object type: object @@ -4632,14 +4839,6 @@ definitions: description: >- the timeline reference id (project id or phase id, corresponding to the `reference`) - TimelineBodyParam: - title: Timeline body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/TimelineRequest' Timeline: title: Timeline object allOf: @@ -4674,51 +4873,6 @@ definitions: description: READ-ONLY. User that last updated this object readOnly: true - $ref: '#/definitions/TimelineRequest' - TimelineResponse: - title: Single timeline response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/Timeline' - TimelineListResponse: - title: Timeline list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/Timeline' MilestonePostRequest: title: Milestone request object type: object @@ -4834,22 +4988,6 @@ definitions: blockedText: type: string description: the milestone blocked text - MilestonePostBodyParam: - title: Milestone body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/MilestonePostRequest' - MilestonePatchBodyParam: - title: Milestone body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/MilestonePatchRequest' Milestone: title: Milestone object allOf: @@ -4884,51 +5022,6 @@ definitions: description: READ-ONLY. User that last updated this object readOnly: true - $ref: '#/definitions/MilestonePostRequest' - MilestoneResponse: - title: Single milestone response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/Milestone' - MilestoneListResponse: - title: Milestone list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/Milestone' MilestoneTemplateRequest: title: Milestone template request object type: object @@ -4971,14 +5064,6 @@ definitions: metadata: type: object description: the milestone template metadata - MilestoneTemplateBodyParam: - title: Milestone template body param - type: object - required: - - param - properties: - param: - $ref: '#/definitions/MilestoneTemplateRequest' MilestoneCloneTemplateRequest: title: Milestone clone template request object type: object @@ -5042,94 +5127,30 @@ definitions: description: READ-ONLY. User that last updated this object readOnly: true - $ref: '#/definitions/MilestoneTemplateRequest' - MilestoneTemplateResponse: - title: Single milestone template response object - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/MilestoneTemplate' - MilestoneTemplateListResponse: - title: Milestone template list response object - type: object - properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: array - items: - $ref: '#/definitions/MilestoneTemplate' AllMetadataResponse: title: All metadata response object type: object properties: - id: - type: string - readOnly: true - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: object - properties: - projectTemplates: - type: array - items: - $ref: '#/definitions/ProjectTemplate' - productTemplates: - type: array - items: - $ref: '#/definitions/ProductTemplate' - milestoneTemplates: - type: array - items: - $ref: '#/definitions/MilestoneTemplate' - projectTypes: - type: array - items: - $ref: '#/definitions/ProjectType' - productCategories: - type: array - items: - $ref: '#/definitions/ProductCategory' + projectTemplates: + type: array + items: + $ref: '#/definitions/ProjectTemplate' + productTemplates: + type: array + items: + $ref: '#/definitions/ProductTemplate' + milestoneTemplates: + type: array + items: + $ref: '#/definitions/MilestoneTemplate' + projectTypes: + type: array + items: + $ref: '#/definitions/ProjectType' + productCategories: + type: array + items: + $ref: '#/definitions/ProductCategory' ProjectMemberInvite: type: object properties: @@ -5194,70 +5215,43 @@ definitions: title: Add project member invites request object type: object properties: - param: - type: object - properties: - userIds: - description: 'The user Id list, could not present with emails' - type: array - items: - type: integer - format: int64 - emails: - type: array - items: - type: string - description: 'The user email list, could not present with userIds' - role: - description: The target role in the project - type: string - enum: - - manager - - customer - - copilot + userIds: + description: 'The user Id list, could not present with emails' + type: array + items: + type: integer + format: int64 + emails: + type: array + items: + type: string + description: 'The user email list, could not present with userIds' + role: + description: The target role in the project + type: string + enum: + - manager + - customer + - copilot UpdateProjectMemberInviteRequest: title: Update project member invite request object type: object properties: - param: - 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 - enum: - - pending - - accepted - - refused - - canceled - ProjectMemberInviteResponse: - title: Project member invite response object - type: object - properties: - id: + userId: + type: integer + format: int64 + description: 'The user Id, could not present with email' + email: type: string - description: unique id identifying the request - version: + description: 'The user email, could not present with userId' + status: + description: The invite status type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - $ref: '#/definitions/ProjectMemberInviteSuccessAndFailure' + enum: + - pending + - accepted + - refused + - canceled Form: type: object properties: @@ -5297,54 +5291,11 @@ definitions: format: int64 description: READ-ONLY. User that last updated this task readOnly: true - FormResponse: - title: Form response - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - $ref: '#/definitions/Form' - FormListResponse: - title: From list response - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - type: array - items: - $ref: '#/definitions/Form' - NewFormParam: + NewForm: type: object properties: - param: + config: type: object - properties: - config: - type: object PriceConfig: type: object properties: @@ -5384,55 +5335,12 @@ definitions: format: int64 description: READ-ONLY. User that last updated this task readOnly: true - PriceConfigResponse: - title: PriceConfig response - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - $ref: '#/definitions/PriceConfig' - PriceConfigListResponse: - title: PriceConfig list response - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - type: array - items: - $ref: '#/definitions/Form' - NewPriceConfigParam: + NewPriceConfig: type: object properties: - param: + config: + description: config json type: object - properties: - config: - description: config json - type: object PlanConfig: type: object properties: @@ -5472,52 +5380,9 @@ definitions: format: int64 description: READ-ONLY. User that last updated this task readOnly: true - PlanConfigResponse: - title: PlanConfig response - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - $ref: '#/definitions/PlanConfig' - PlanConfigListResponse: - title: PlanConfig list response - type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: string - description: http status code - content: - type: array - items: - $ref: '#/definitions/PlanConfig' - NewPlanConfigParam: + NewPlanConfig: type: object properties: - param: + config: + description: config json type: object - properties: - config: - description: config json - type: object diff --git a/package-lock.json b/package-lock.json index 10c0fce6..33011b24 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": { @@ -18,98 +18,15 @@ "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.0.tgz", "integrity": "sha1-JjNHCk6r6aR82aRf2yDtX5NAe8o=" }, - "@types/body-parser": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", - "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", - "requires": { - "@types/connect": "3.4.32", - "@types/node": "10.12.15" - } - }, - "@types/connect": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", - "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", - "requires": { - "@types/node": "10.12.15" - } - }, - "@types/events": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" - }, - "@types/express": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.0.tgz", - "integrity": "sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==", - "requires": { - "@types/body-parser": "1.17.0", - "@types/express-serve-static-core": "4.16.0", - "@types/serve-static": "1.13.2" - } - }, - "@types/express-jwt": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.34.tgz", - "integrity": "sha1-/b7kxq9cCiRu8qkz9VGZc8dxfwI=", - "requires": { - "@types/express": "4.16.0", - "@types/express-unless": "0.0.32" - } - }, - "@types/express-serve-static-core": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz", - "integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", - "requires": { - "@types/events": "1.2.0", - "@types/node": "10.12.15", - "@types/range-parser": "1.2.3" - } - }, - "@types/express-unless": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.0.32.tgz", - "integrity": "sha512-6YpJyFNlDDnPnRjMOvJCoDYlSDDmG/OEEUsPk7yhNkL4G9hUYtgab6vi1CcWsGSSSM0CsvNlWTG+ywAGnvF03g==", - "requires": { - "@types/express": "4.16.0" - } - }, "@types/geojson": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" }, "@types/lodash": { - "version": "4.14.116", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.116.tgz", - "integrity": "sha512-lRnAtKnxMXcYYXqOiotTmJd74uawNWuPnsnPrrO7HiFuE3npE2iQhfABatbYDyxTNqZNuXzcKGhw37R7RjBFLg==" - }, - "@types/mime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", - "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==" - }, - "@types/node": { - "version": "10.12.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", - "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==" - }, - "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" - }, - "@types/serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", - "requires": { - "@types/express-serve-static-core": "4.16.0", - "@types/mime": "2.0.0" - } + "version": "4.14.133", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.133.tgz", + "integrity": "sha512-/3JqnvPnY58GLzG3Y7fpphOhATV1DDZ/Ak3DQufjlRK5E4u+s0CfClfNFtAGBabw+jDGtRFbOZe+Z02ZMWCBNQ==" }, "abbrev": { "version": "1.0.9", @@ -122,7 +39,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", "requires": { - "mime-types": "2.1.17", + "mime-types": "~2.1.16", "negotiator": "0.6.1" } }, @@ -138,7 +55,7 @@ "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "dev": true, "requires": { - "acorn": "3.3.0" + "acorn": "^3.0.4" }, "dependencies": { "acorn": { @@ -155,8 +72,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" } }, "ajv-keywords": { @@ -171,9 +88,9 @@ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" } }, "amdefine": { @@ -187,10 +104,10 @@ "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.1.tgz", "integrity": "sha1-fMz+ur5WwumE6noiQ/fO/m+/xs8=", "requires": { - "bitsyntax": "0.0.4", - "bluebird": "3.5.1", + "bitsyntax": "~0.0.4", + "bluebird": "^3.4.6", "buffer-more-ints": "0.0.2", - "readable-stream": "1.1.14" + "readable-stream": "1.x >=1.1.9" } }, "analytics-node": { @@ -198,15 +115,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.11.0", - "crypto-token": "1.0.1", - "debug": "2.6.9", - "lodash": "4.17.4", - "remove-trailing-slash": "0.1.0", - "superagent": "3.8.0", - "superagent-retry": "0.6.0" + "@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" } }, "ansi-align": { @@ -215,7 +132,7 @@ "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "dev": true, "requires": { - "string-width": "2.1.1" + "string-width": "^2.0.0" }, "dependencies": { "ansi-regex": { @@ -236,8 +153,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": { @@ -246,7 +163,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -272,9 +189,10 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "dev": true, + "optional": true, "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" } }, "app-module-path": { @@ -288,7 +206,7 @@ "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", "dev": true, "requires": { - "default-require-extensions": "1.0.0" + "default-require-extensions": "^1.0.0" } }, "archy": { @@ -303,7 +221,7 @@ "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "arr-diff": { @@ -312,7 +230,7 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "1.1.0" + "arr-flatten": "^1.0.1" } }, "arr-flatten": { @@ -321,6 +239,12 @@ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, "array-differ": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", @@ -350,7 +274,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "1.0.3" + "array-uniq": "^1.0.1" } }, "array-uniq": { @@ -381,7 +305,7 @@ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "~2.1.0" } }, "assert-plus": { @@ -395,6 +319,12 @@ "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", "dev": true }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -412,8 +342,8 @@ "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.8.tgz", "integrity": "sha512-1Sy1jDhjlgxcSd9/ICHqiAHT8VSJ9R1lzEyWwP/4Hm9p8nVTNtU0SxG/Z15XHD/aZvQraSw9BpDU3EBcFnOVrw==", "requires": { - "semver": "5.4.1", - "shimmer": "1.1.0" + "semver": "^5.3.0", + "shimmer": "^1.1.0" } }, "asynckit": { @@ -421,18 +351,24 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, "auth0-js": { - "version": "9.8.2", - "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.8.2.tgz", - "integrity": "sha512-fwUkIABBA0e1B6hfkePtjOFlhXzvOUc/ZFx3NE1X9Ij3VZeqtJK7QU/Pc6tar+NkOpgZbRUXkxEG5qPGiwixWQ==", - "requires": { - "base64-js": "1.2.1", - "idtoken-verifier": "1.2.0", - "js-cookie": "2.2.0", - "qs": "6.5.1", - "superagent": "3.8.3", - "url-join": "4.0.0", - "winchan": "0.2.1" + "version": "9.10.4", + "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.10.4.tgz", + "integrity": "sha512-FKwwA0E9NDrn/MxCzeftA5wx6YCos9wINhuvXeFHDdx+Lis7ykR46kBXJF4+dYjDdC5QhQ7W0cAp6bBS5gS75Q==", + "requires": { + "base64-js": "^1.2.0", + "idtoken-verifier": "^1.2.0", + "js-cookie": "^2.2.0", + "qs": "^6.4.0", + "superagent": "^3.8.2", + "url-join": "^4.0.0", + "winchan": "^0.2.1" }, "dependencies": { "debug": { @@ -440,7 +376,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" } }, "formidable": { @@ -465,24 +401,24 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "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.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "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.1" + "safe-buffer": "~5.1.0" } }, "superagent": { @@ -490,42 +426,34 @@ "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.1", - "debug": "3.2.6", - "extend": "3.0.1", - "form-data": "2.3.1", - "formidable": "1.2.1", - "methods": "1.1.2", - "mime": "1.4.1", - "qs": "6.5.1", - "readable-stream": "2.3.6" + "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" } } } }, "aws-sdk": { - "version": "2.143.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.143.0.tgz", - "integrity": "sha1-dRBovUzxPTJl9tobAlSQO+S6yO4=", + "version": "2.468.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.468.0.tgz", + "integrity": "sha512-Bo4j1DLDBWSLgNsfpLNU2RKU2+24JzdFqgyyOKOyJ1p6RgrnDxcwoR2CPWK5olPU2cgXmIicetfOGDsC3LjLtg==", "requires": { "buffer": "4.9.1", - "crypto-browserify": "1.0.9", "events": "1.1.1", + "ieee754": "1.1.8", "jmespath": "0.15.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", - "uuid": "3.1.0", - "xml2js": "0.4.17", - "xmlbuilder": "4.2.1" - }, - "dependencies": { - "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" - } + "uuid": "3.3.2", + "xml2js": "0.4.19" } }, "aws-sign2": { @@ -539,12 +467,19 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "axios": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz", - "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", "requires": { - "follow-redirects": "1.2.6", - "is-buffer": "1.1.6" + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + } } }, "babel-cli": { @@ -553,21 +488,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.7.0", - "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.7", - "v8flags": "2.1.1" + "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" }, "dependencies": { "babel-runtime": { @@ -576,8 +511,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "glob": { @@ -586,12 +521,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "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" } } } @@ -602,36 +537,36 @@ "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.2", + "js-tokens": "^3.0.2" } }, "babel-core": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", - "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", - "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.0", - "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" + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "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" }, "dependencies": { "babel-runtime": { @@ -640,8 +575,17 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" } }, "json5": { @@ -658,26 +602,26 @@ "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0" + "babel-code-frame": "^6.22.0", + "babel-traverse": "^6.23.1", + "babel-types": "^6.23.0", + "babylon": "^6.17.0" } }, "babel-generator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", - "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=", - "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" + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "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" }, "dependencies": { "babel-runtime": { @@ -686,8 +630,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -698,10 +642,10 @@ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -710,8 +654,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -722,10 +666,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.4" }, "dependencies": { "babel-runtime": { @@ -734,8 +678,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -746,11 +690,11 @@ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "dev": true, "requires": { - "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" + "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" }, "dependencies": { "babel-runtime": { @@ -759,8 +703,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -771,8 +715,8 @@ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -781,8 +725,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -793,8 +737,8 @@ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -803,8 +747,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -815,8 +759,8 @@ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -825,8 +769,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -837,9 +781,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.4" }, "dependencies": { "babel-runtime": { @@ -848,8 +792,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -860,12 +804,12 @@ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", "dev": true, "requires": { - "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" + "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" }, "dependencies": { "babel-runtime": { @@ -874,8 +818,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -886,8 +830,8 @@ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -896,8 +840,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -908,7 +852,7 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -917,8 +861,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -935,7 +879,7 @@ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -944,8 +888,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -956,7 +900,7 @@ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -965,8 +909,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -977,7 +921,7 @@ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -986,8 +930,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -998,11 +942,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.4" }, "dependencies": { "babel-runtime": { @@ -1011,8 +955,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1023,15 +967,15 @@ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { - "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" + "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" }, "dependencies": { "babel-runtime": { @@ -1040,8 +984,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1052,8 +996,8 @@ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -1062,8 +1006,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1074,7 +1018,7 @@ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -1083,8 +1027,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1095,8 +1039,8 @@ "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -1105,8 +1049,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1117,7 +1061,7 @@ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -1126,8 +1070,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1138,9 +1082,9 @@ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -1149,8 +1093,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1161,7 +1105,7 @@ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -1170,8 +1114,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1182,9 +1126,9 @@ "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -1193,22 +1137,22 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } }, "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", - "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "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": { @@ -1217,8 +1161,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1229,9 +1173,9 @@ "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", "dev": true, "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -1240,8 +1184,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1252,9 +1196,9 @@ "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", "dev": true, "requires": { - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -1263,8 +1207,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1275,8 +1219,8 @@ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", "dev": true, "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.26.0" + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -1285,8 +1229,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1297,12 +1241,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.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" + "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" }, "dependencies": { "babel-runtime": { @@ -1311,8 +1255,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1323,8 +1267,8 @@ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -1333,8 +1277,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1345,7 +1289,7 @@ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -1354,8 +1298,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1366,9 +1310,9 @@ "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", "dev": true, "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -1377,8 +1321,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1389,7 +1333,7 @@ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -1398,8 +1342,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1410,7 +1354,7 @@ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -1419,8 +1363,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1431,9 +1375,9 @@ "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", "dev": true, "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "regexpu-core": "2.0.0" + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" }, "dependencies": { "babel-runtime": { @@ -1442,8 +1386,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1454,7 +1398,7 @@ "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", "dev": true, "requires": { - "regenerator-transform": "0.10.1" + "regenerator-transform": "^0.10.0" } }, "babel-plugin-transform-runtime": { @@ -1463,7 +1407,7 @@ "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" }, "dependencies": { "babel-runtime": { @@ -1472,8 +1416,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1484,8 +1428,8 @@ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" }, "dependencies": { "babel-runtime": { @@ -1494,8 +1438,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1506,9 +1450,9 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "core-js": "2.5.1", - "regenerator-runtime": "0.10.5" + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" }, "dependencies": { "babel-runtime": { @@ -1517,8 +1461,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" }, "dependencies": { "regenerator-runtime": { @@ -1543,30 +1487,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.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.0", - "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-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-register": { @@ -1575,13 +1519,13 @@ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-core": "6.26.0", - "babel-runtime": "6.26.0", - "core-js": "2.5.1", - "home-or-tmp": "2.0.0", - "lodash": "4.17.4", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" + "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" }, "dependencies": { "babel-runtime": { @@ -1590,18 +1534,18 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } }, "babel-runtime": { "version": "6.6.1", - "resolved": "http://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz", "integrity": "sha1-eIuUtvY04luRvWxd9y1GdFevsAA=", "requires": { - "core-js": "2.5.1" + "core-js": "^2.1.0" } }, "babel-template": { @@ -1610,11 +1554,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.4" }, "dependencies": { "babel-runtime": { @@ -1623,8 +1567,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1635,15 +1579,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.9", - "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.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" }, "dependencies": { "babel-runtime": { @@ -1652,8 +1596,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1664,10 +1608,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.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" }, "dependencies": { "babel-runtime": { @@ -1676,8 +1620,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -1693,7 +1637,7 @@ "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", "requires": { - "precond": "0.2.3" + "precond": "0.2" } }, "balanced-match": { @@ -1701,6 +1645,73 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, "base64-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", @@ -1711,7 +1722,7 @@ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "beeper": { @@ -1721,13 +1732,13 @@ "dev": true }, "bin-protocol": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/bin-protocol/-/bin-protocol-3.0.4.tgz", - "integrity": "sha1-RlqdNQb+sOEmtStbIWDZNuFbJ/Q=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bin-protocol/-/bin-protocol-3.1.1.tgz", + "integrity": "sha512-9vCGfaHC2GBHZwGQdG+DpyXfmLvx9uKtf570wMLwIc9wmTIDgsdCBXQxTZu5X2GyogkfBks2Ode4N0sUVxJ2qQ==", "requires": { - "lodash": "4.17.4", - "long": "3.2.0", - "protocol-buffers-schema": "3.3.2" + "lodash": "^4.17.11", + "long": "^4.0.0", + "protocol-buffers-schema": "^3.0.0" } }, "binary-extensions": { @@ -1760,30 +1771,30 @@ "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.1", - "http-errors": "1.6.2", + "depd": "~1.1.1", + "http-errors": "~1.6.2", "iconv-lite": "0.4.19", - "on-finished": "2.3.0", + "on-finished": "~2.3.0", "qs": "6.5.1", "raw-body": "2.3.2", - "type-is": "1.6.15" + "type-is": "~1.6.15" } }, "boxen": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.2.2.tgz", - "integrity": "sha1-Px1AMsMP/qnUsCwyLq8up0HcvOU=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", "dev": true, "requires": { - "ansi-align": "2.0.0", - "camelcase": "4.1.0", - "chalk": "2.3.0", - "cli-boxes": "1.0.0", - "string-width": "2.1.1", - "term-size": "1.2.0", - "widest-line": "1.0.0" + "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" }, "dependencies": { "ansi-regex": { @@ -1793,12 +1804,12 @@ "dev": true }, "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "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==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "camelcase": { @@ -1808,20 +1819,20 @@ "dev": true }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "is-fullwidth-code-point": { @@ -1836,8 +1847,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": { @@ -1846,16 +1857,16 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "^3.0.0" } } } @@ -1865,7 +1876,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -1875,9 +1886,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" } }, "buffer": { @@ -1885,9 +1896,9 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "requires": { - "base64-js": "1.2.1", - "ieee754": "1.1.8", - "isarray": "1.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" }, "dependencies": { "isarray": { @@ -1928,10 +1939,10 @@ "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", "requires": { - "dtrace-provider": "0.8.5", - "moment": "2.22.2", - "mv": "2.1.1", - "safe-json-stringify": "1.0.4" + "dtrace-provider": "~0.8", + "moment": "^2.10.6", + "mv": "~2", + "safe-json-stringify": "~1" } }, "bytes": { @@ -1939,13 +1950,38 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "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" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "0.2.0" + "callsites": "^0.2.0" } }, "callsites": { @@ -1962,9 +1998,9 @@ "optional": true }, "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", "dev": true }, "caseless": { @@ -1979,8 +2015,8 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" } }, "chai": { @@ -1989,9 +2025,9 @@ "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", "dev": true, "requires": { - "assertion-error": "1.0.2", - "deep-eql": "0.1.3", - "type-detect": "1.0.0" + "assertion-error": "^1.0.1", + "deep-eql": "^0.1.3", + "type-detect": "^1.0.0" } }, "chai-as-promised": { @@ -2000,7 +2036,7 @@ "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, "requires": { - "check-error": "1.0.2" + "check-error": "^1.0.2" } }, "chalk": { @@ -2008,11 +2044,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.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "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" } }, "check-error": { @@ -2032,23 +2068,60 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "dev": true, + "optional": true, "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" + "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" } }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "cli-boxes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", @@ -2061,12 +2134,12 @@ "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=", "dev": true, "requires": { - "ansi-regex": "2.1.1", - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-iterator": "2.0.3", - "memoizee": "0.4.11", - "timers-ext": "0.1.2" + "ansi-regex": "^2.1.1", + "d": "1", + "es5-ext": "^0.10.12", + "es6-iterator": "2", + "memoizee": "^0.4.3", + "timers-ext": "0.1" } }, "cli-cursor": { @@ -2075,7 +2148,7 @@ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "dev": true, "requires": { - "restore-cursor": "1.0.1" + "restore-cursor": "^1.0.1" } }, "cli-width": { @@ -2091,8 +2164,8 @@ "dev": true, "optional": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" }, "dependencies": { @@ -2137,15 +2210,25 @@ "dependencies": { "semver": { "version": "5.0.1", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.1.tgz", "integrity": "sha1-n7P0AE+QDYPEeWj+QvdYPgWDLMk=" } } }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, "color-convert": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "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" @@ -2167,7 +2250,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { @@ -2190,7 +2273,7 @@ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", "requires": { - "mime-db": "1.37.0" + "mime-db": ">= 1.36.0 < 2" }, "dependencies": { "mime-db": { @@ -2205,13 +2288,13 @@ "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "2.0.15", + "compressible": "~2.0.14", "debug": "2.6.9", - "on-headers": "1.0.1", + "on-headers": "~1.0.1", "safe-buffer": "5.1.2", - "vary": "1.1.2" + "vary": "~1.1.2" }, "dependencies": { "accepts": { @@ -2219,7 +2302,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "requires": { - "mime-types": "2.1.21", + "mime-types": "~2.1.18", "negotiator": "0.6.1" } }, @@ -2233,7 +2316,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "1.37.0" + "mime-db": "~1.37.0" } }, "safe-buffer": { @@ -2254,9 +2337,9 @@ "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "typedarray": "0.0.6" + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" }, "dependencies": { "isarray": { @@ -2271,13 +2354,13 @@ "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -2286,7 +2369,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -2306,22 +2389,22 @@ "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", "dev": true, "requires": { - "ini": "1.3.4", - "proto-list": "1.2.4" + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, "configstore": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz", - "integrity": "sha512-5oNkD/L++l0O6xGXxb1EWS7SivtjfGQlRyxJsYgE0Z495/L81e2h4/d3r969hoPXuFItzNOKMtsXgYG4c7dYvw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", "dev": true, "requires": { - "dot-prop": "4.2.0", - "graceful-fs": "4.1.11", - "make-dir": "1.1.0", - "unique-string": "1.0.0", - "write-file-atomic": "2.3.0", - "xdg-basedir": "3.0.0" + "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" } }, "connection-parse": { @@ -2350,8 +2433,8 @@ "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.0.tgz", "integrity": "sha1-4Z/Da1lwkKXU5KOy6j68XilpSiQ=", "requires": { - "async-listener": "0.6.8", - "emitter-listener": "1.0.1" + "async-listener": "^0.6.0", + "emitter-listener": "^1.0.1" } }, "convert-source-map": { @@ -2375,6 +2458,12 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, "core-js": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", @@ -2390,8 +2479,8 @@ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", "requires": { - "object-assign": "4.1.1", - "vary": "1.1.2" + "object-assign": "^4", + "vary": "^1" } }, "create-error-class": { @@ -2400,7 +2489,7 @@ "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "dev": true, "requires": { - "capture-stack-trace": "1.0.0" + "capture-stack-trace": "^1.0.0" } }, "cross-spawn": { @@ -2409,9 +2498,9 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.1", - "shebang-command": "1.2.0", - "which": "1.3.0" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "dependencies": { "lru-cache": { @@ -2420,17 +2509,12 @@ "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } } } }, - "crypto-browserify": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-1.0.9.tgz", - "integrity": "sha1-zFRJaF37hesRyYKKzHy4erW7/MA=" - }, "crypto-js": { "version": "3.1.9-1", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", @@ -2453,7 +2537,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.35" + "es5-ext": "^0.10.9" } }, "dashdash": { @@ -2461,7 +2545,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" } }, "dateformat": { @@ -2484,6 +2568,12 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, "deep-eql": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", @@ -2502,9 +2592,9 @@ } }, "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, "deep-is": { @@ -2519,7 +2609,7 @@ "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", "dev": true, "requires": { - "strip-bom": "2.0.0" + "strip-bom": "^2.0.0" }, "dependencies": { "strip-bom": { @@ -2528,7 +2618,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } } } @@ -2539,7 +2629,7 @@ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "dev": true, "requires": { - "clone": "1.0.2" + "clone": "^1.0.2" }, "dependencies": { "clone": { @@ -2550,19 +2640,72 @@ } } }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, "del": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.0", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.4.5" + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" } }, "delayed-stream": { @@ -2592,7 +2735,7 @@ "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", "dev": true, "requires": { - "fs-exists-sync": "0.1.0" + "fs-exists-sync": "^0.1.0" } }, "detect-indent": { @@ -2601,7 +2744,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "diff": { @@ -2616,8 +2759,8 @@ "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" + "esutils": "^2.0.2", + "isarray": "^1.0.0" }, "dependencies": { "isarray": { @@ -2634,7 +2777,7 @@ "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "dev": true, "requires": { - "is-obj": "1.0.1" + "is-obj": "^1.0.0" } }, "dottie": { @@ -2648,22 +2791,16 @@ "integrity": "sha1-mOu6Ihr6xG4cOf02hY2Pk2dSS5I=", "optional": true, "requires": { - "nan": "2.7.0" + "nan": "^2.3.3" } }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true - }, "duplexer2": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", "dev": true, "requires": { - "readable-stream": "1.1.14" + "readable-stream": "~1.1.9" } }, "duplexer3": { @@ -2677,8 +2814,8 @@ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "ecdsa-sig-formatter": { @@ -2686,7 +2823,7 @@ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "editorconfig": { @@ -2695,11 +2832,11 @@ "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", "dev": true, "requires": { - "bluebird": "3.5.1", - "commander": "2.11.0", - "lru-cache": "3.2.0", - "semver": "5.4.1", - "sigmund": "1.0.1" + "bluebird": "^3.0.5", + "commander": "^2.9.0", + "lru-cache": "^3.2.0", + "semver": "^5.1.0", + "sigmund": "^1.0.1" }, "dependencies": { "lru-cache": { @@ -2708,7 +2845,7 @@ "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", "dev": true, "requires": { - "pseudomap": "1.0.2" + "pseudomap": "^1.0.1" } } } @@ -2723,11 +2860,11 @@ "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-11.0.1.tgz", "integrity": "sha1-0YBoTGvefs+g+iTmL6HIcu6uCOc=", "requires": { - "chalk": "1.1.3", - "forever-agent": "0.6.1", - "lodash": "3.10.1", - "lodash-compat": "3.10.2", - "promise": "7.3.1" + "chalk": "^1.0.0", + "forever-agent": "^0.6.0", + "lodash": "^3.10.0", + "lodash-compat": "^3.0.0", + "promise": "^7.1.1" }, "dependencies": { "lodash": { @@ -2763,7 +2900,7 @@ "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", "dev": true, "requires": { - "once": "1.3.3" + "once": "~1.3.0" }, "dependencies": { "once": { @@ -2772,7 +2909,7 @@ "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } } } @@ -2783,7 +2920,7 @@ "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es5-ext": { @@ -2792,8 +2929,8 @@ "integrity": "sha1-GO6FjOajxFx9eekcFfzKnsVoSU8=", "dev": true, "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" + "es6-iterator": "~2.0.1", + "es6-symbol": "~3.1.1" } }, "es6-iterator": { @@ -2802,9 +2939,9 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, "es6-map": { @@ -2813,31 +2950,25 @@ "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" + "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" } }, - "es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", - "dev": true - }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-iterator": "2.0.3", + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" + "event-emitter": "~0.3.5" } }, "es6-symbol": { @@ -2846,8 +2977,8 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35" + "d": "1", + "es5-ext": "~0.10.14" } }, "es6-weak-map": { @@ -2856,10 +2987,10 @@ "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" } }, "escape-html": { @@ -2878,10 +3009,10 @@ "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", "dev": true, "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.2", - "esrecurse": "4.2.0", - "estraverse": "4.2.0" + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "eslint": { @@ -2890,41 +3021,41 @@ "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "chalk": "1.1.3", - "concat-stream": "1.6.0", - "debug": "2.6.9", - "doctrine": "2.0.0", - "escope": "3.6.0", - "espree": "3.5.1", - "esquery": "1.0.0", - "estraverse": "4.2.0", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "glob": "7.1.2", - "globals": "9.18.0", - "ignore": "3.3.7", - "imurmurhash": "0.1.4", - "inquirer": "0.12.0", - "is-my-json-valid": "2.16.1", - "is-resolvable": "1.0.0", - "js-yaml": "3.10.0", - "json-stable-stringify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "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" + "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" }, "dependencies": { "glob": { @@ -2933,12 +3064,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "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" } }, "user-home": { @@ -2947,7 +3078,7 @@ "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", "dev": true, "requires": { - "os-homedir": "1.0.2" + "os-homedir": "^1.0.0" } } } @@ -2958,7 +3089,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": { @@ -2967,8 +3098,8 @@ "integrity": "sha512-yUtXS15gIcij68NmXmP9Ni77AQuCN0itXbCc/jWd8C6/yKZaSNXicpC8cgvjnxVdmfsosIXrjpzFq7GcDryb6A==", "dev": true, "requires": { - "debug": "2.6.9", - "resolve": "1.5.0" + "debug": "^2.6.8", + "resolve": "^1.2.0" } }, "eslint-module-utils": { @@ -2977,8 +3108,8 @@ "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", "dev": true, "requires": { - "debug": "2.6.9", - "pkg-dir": "1.0.0" + "debug": "^2.6.8", + "pkg-dir": "^1.0.0" } }, "eslint-plugin-import": { @@ -2987,16 +3118,16 @@ "integrity": "sha512-Rf7dfKJxZ16QuTgVv1OYNxkZcsu/hULFnC+e+w0Gzi6jMC3guQoWQgxYxc54IDRinlb6/0v5z/PxxIKmVctN+g==", "dev": true, "requires": { - "builtin-modules": "1.1.1", - "contains-path": "0.1.0", - "debug": "2.6.9", + "builtin-modules": "^1.1.1", + "contains-path": "^0.1.0", + "debug": "^2.6.8", "doctrine": "1.5.0", - "eslint-import-resolver-node": "0.3.1", - "eslint-module-utils": "2.1.1", - "has": "1.0.1", - "lodash.cond": "4.5.2", - "minimatch": "3.0.4", - "read-pkg-up": "2.0.0" + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.1.1", + "has": "^1.0.1", + "lodash.cond": "^4.3.0", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0" }, "dependencies": { "doctrine": { @@ -3005,8 +3136,8 @@ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" + "esutils": "^2.0.2", + "isarray": "^1.0.0" } }, "isarray": { @@ -3029,8 +3160,8 @@ "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=", "dev": true, "requires": { - "acorn": "5.2.1", - "acorn-jsx": "3.0.1" + "acorn": "^5.1.1", + "acorn-jsx": "^3.0.0" } }, "esprima": { @@ -3045,7 +3176,7 @@ "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.0.0" } }, "esrecurse": { @@ -3054,8 +3185,8 @@ "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", "dev": true, "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" + "estraverse": "^4.1.0", + "object-assign": "^4.0.1" } }, "estraverse": { @@ -3081,23 +3212,8 @@ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35" - } - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "dev": true, - "requires": { - "duplexer": "0.1.1", - "from": "0.1.7", - "map-stream": "0.1.0", - "pause-stream": "0.0.11", - "split": "0.3.3", - "stream-combiner": "0.0.4", - "through": "2.3.8" + "d": "1", + "es5-ext": "~0.10.14" } }, "events": { @@ -3111,13 +3227,13 @@ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "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" + "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" } }, "exit-hook": { @@ -3132,7 +3248,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "0.1.1" + "is-posix-bracket": "^0.1.0" } }, "expand-range": { @@ -3141,7 +3257,7 @@ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { - "fill-range": "2.2.3" + "fill-range": "^2.1.0" } }, "expand-tilde": { @@ -3150,7 +3266,7 @@ "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", "dev": true, "requires": { - "os-homedir": "1.0.2" + "os-homedir": "^1.0.1" } }, "express": { @@ -3158,36 +3274,36 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", "requires": { - "accepts": "1.3.4", + "accepts": "~1.3.4", "array-flatten": "1.1.1", "body-parser": "1.18.2", "content-disposition": "0.5.2", - "content-type": "1.0.4", + "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.1", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.1", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "finalhandler": "1.1.0", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.2", + "proxy-addr": "~2.0.2", "qs": "6.5.1", - "range-parser": "1.2.0", + "range-parser": "~1.2.0", "safe-buffer": "5.1.1", "send": "0.16.1", "serve-static": "1.13.1", "setprototypeof": "1.1.0", - "statuses": "1.3.1", - "type-is": "1.6.15", + "statuses": "~1.3.1", + "type-is": "~1.6.15", "utils-merge": "1.0.1", - "vary": "1.1.2" + "vary": "~1.1.2" }, "dependencies": { "setprototypeof": { @@ -3207,8 +3323,8 @@ "resolved": "https://registry.npmjs.org/express-list-routes/-/express-list-routes-0.1.4.tgz", "integrity": "sha1-xlwxw/thnHnAVD97TsToMFbs5hY=", "requires": { - "colors": "1.1.2", - "lodash": "3.10.1" + "colors": "^1.0.3", + "lodash": "^3.0.0" }, "dependencies": { "lodash": { @@ -3223,7 +3339,7 @@ "resolved": "https://registry.npmjs.org/express-request-id/-/express-request-id-1.4.0.tgz", "integrity": "sha1-J3ssCUmAPmgQTJ1Fw+aJNPlr9aI=", "requires": { - "uuid": "3.3.2" + "uuid": "^3.0.1" } }, "express-sanitizer": { @@ -3239,7 +3355,7 @@ "resolved": "https://registry.npmjs.org/express-validation/-/express-validation-0.6.0.tgz", "integrity": "sha1-DXf0r8flixIBat7FmzJb7v2dwmg=", "requires": { - "lodash": "4.17.4" + "lodash": "^4.9.0" } }, "extend": { @@ -3247,13 +3363,34 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "extsprintf": { @@ -3267,8 +3404,8 @@ "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", "dev": true, "requires": { - "chalk": "1.1.3", - "time-stamp": "1.1.0" + "chalk": "^1.1.1", + "time-stamp": "^1.0.0" } }, "fast-deep-equal": { @@ -3293,8 +3430,8 @@ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" } }, "file-entry-cache": { @@ -3303,8 +3440,8 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "1.3.0", - "object-assign": "4.1.1" + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" } }, "filename-regex": { @@ -3319,8 +3456,8 @@ "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", "dev": true, "requires": { - "glob": "7.1.2", - "minimatch": "3.0.4" + "glob": "^7.0.3", + "minimatch": "^3.0.3" }, "dependencies": { "glob": { @@ -3329,27 +3466,54 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "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" } } } }, "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + } } }, "finalhandler": { @@ -3358,12 +3522,12 @@ "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", "requires": { "debug": "2.6.9", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" }, "dependencies": { "statuses": { @@ -3385,8 +3549,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "findup-sync": { @@ -3395,10 +3559,10 @@ "integrity": "sha1-b35LV7buOkA3tEFOrt6j9Y9x4Ow=", "dev": true, "requires": { - "detect-file": "0.1.0", - "is-glob": "2.0.1", - "micromatch": "2.3.11", - "resolve-dir": "0.1.1" + "detect-file": "^0.1.0", + "is-glob": "^2.0.1", + "micromatch": "^2.3.7", + "resolve-dir": "^0.1.0" } }, "fined": { @@ -3407,11 +3571,11 @@ "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", "dev": true, "requires": { - "expand-tilde": "2.0.2", - "is-plain-object": "2.0.4", - "object.defaults": "1.1.0", - "object.pick": "1.3.0", - "parse-filepath": "1.0.1" + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" }, "dependencies": { "expand-tilde": { @@ -3420,7 +3584,7 @@ "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "dev": true, "requires": { - "homedir-polyfill": "1.0.1" + "homedir-polyfill": "^1.0.1" } } } @@ -3443,18 +3607,18 @@ "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" } }, "follow-redirects": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.6.tgz", - "integrity": "sha512-FrMqZ/FONtHnbqO651UPpfRUVukIEwJhXMfdr/JWAmrDbeYBu773b1J6gdWDyRIj4hvvzQEHoEOTrdR8o6KLYA==", + "version": "1.5.10", + "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": { @@ -3479,7 +3643,7 @@ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { - "for-in": "1.0.2" + "for-in": "^1.0.1" } }, "forever-agent": { @@ -3492,9 +3656,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" } }, "formatio": { @@ -3503,7 +3667,7 @@ "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", "dev": true, "requires": { - "samsam": "1.1.2" + "samsam": "~1.1" } }, "formidable": { @@ -3516,17 +3680,20 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, "fs-exists-sync": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", @@ -3539,9 +3706,9 @@ "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "fs-readdir-recursive": { @@ -3556,375 +3723,917 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "gaze": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", - "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "dev": true, + "optional": true, "requires": { - "globule": "0.1.0" - } - }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "1.0.2" - } - }, - "generic-pool": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz", - "integrity": "sha1-iGvFvwvrfblugby7oHiBjeWmJoM=" - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "2.0.1" - } - }, - "glob-stream": { - "version": "3.1.18", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", - "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", - "dev": true, - "requires": { - "glob": "4.5.3", - "glob2base": "0.0.12", - "minimatch": "2.0.10", - "ordered-read-streams": "0.1.0", - "through2": "0.6.5", - "unique-stream": "1.0.0" + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" }, "dependencies": { - "glob": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", - "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": 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": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "2.0.10", - "once": "1.4.0" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, - "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, "dev": true, "requires": { - "brace-expansion": "1.1.8" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "chownr": { + "version": "1.1.1", + "bundled": true, "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" + "ms": "^2.1.1" } }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "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.5", + "bundled": true, "dev": true, + "optional": true, "requires": { - "readable-stream": "1.0.34", - "xtend": "4.0.1" + "minipass": "^2.2.1" } - } - } - }, - "glob-watcher": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", - "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", - "dev": true, - "requires": { - "gaze": "0.5.2" - } - }, - "glob2base": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", - "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", - "dev": true, - "requires": { - "find-index": "0.1.1" - } - }, - "global-dirs": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.0.tgz", - "integrity": "sha1-ENNAOeDfBCcuJizyQiT3IJQ0308=", - "dev": true, - "requires": { - "ini": "1.3.4" - } - }, - "global-modules": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", - "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", - "dev": true, - "requires": { - "global-prefix": "0.1.5", - "is-windows": "0.2.0" - } - }, - "global-prefix": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", - "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", - "dev": true, - "requires": { - "homedir-polyfill": "1.0.1", - "ini": "1.3.4", - "is-windows": "0.2.0", - "which": "1.3.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, "dev": true, + "optional": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "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" } - } - } - }, - "globule": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", - "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", - "dev": true, - "requires": { - "glob": "3.1.21", - "lodash": "1.0.2", - "minimatch": "0.2.14" - }, - "dependencies": { + }, "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "version": "7.1.3", + "bundled": true, "dev": true, + "optional": true, "requires": { - "graceful-fs": "1.2.3", - "inherits": "1.0.2", - "minimatch": "0.2.14" + "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" } }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true + "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.1", + "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": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "version": "2.0.3", + "bundled": true, "dev": true }, - "lodash": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", - "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", - "dev": true + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true }, "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "version": "3.0.4", + "bundled": true, "dev": true, "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" + "brace-expansion": "^1.1.7" } - } - } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.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" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "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 + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": 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.0", + "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.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": 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.0", + "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, + "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, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "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 + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + } + } }, - "glogg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", - "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gaze": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", + "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", "dev": true, "requires": { - "sparkles": "1.0.0" + "globule": "~0.1.0" } }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", "dev": true, "requires": { - "create-error-class": "3.0.2", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "is-redirect": "1.0.0", - "is-retry-allowed": "1.1.0", - "is-stream": "1.1.0", - "lowercase-keys": "1.0.0", - "safe-buffer": "5.1.1", - "timed-out": "4.0.1", - "unzip-response": "2.0.1", - "url-parse-lax": "1.0.0" + "is-property": "^1.0.0" } }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "generic-pool": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz", + "integrity": "sha1-iGvFvwvrfblugby7oHiBjeWmJoM=" + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", "dev": true }, - "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, - "gulp": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", - "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", - "dev": true, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "archy": "1.0.0", - "chalk": "1.1.3", - "deprecated": "0.0.1", - "gulp-util": "3.0.8", - "interpret": "1.0.4", - "liftoff": "2.3.0", - "minimist": "1.2.0", - "orchestrator": "0.3.8", - "pretty-hrtime": "1.0.3", - "semver": "4.3.6", - "tildify": "1.2.0", - "v8flags": "2.1.1", - "vinyl-fs": "0.3.14" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true - } + "assert-plus": "^1.0.0" } }, - "gulp-help": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/gulp-help/-/gulp-help-1.6.1.tgz", - "integrity": "sha1-Jh2xhuGDl/7z9qLCLpwxW/qIrgw=", - "dev": true, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", "requires": { - "chalk": "1.1.3", - "object-assign": "3.0.0" - }, - "dependencies": { - "object-assign": { - "version": "3.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "glob-stream": { + "version": "3.1.18", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", + "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", + "dev": true, + "requires": { + "glob": "^4.3.1", + "glob2base": "^0.0.12", + "minimatch": "^2.0.1", + "ordered-read-streams": "^0.1.0", + "through2": "^0.6.1", + "unique-stream": "^1.0.0" + }, + "dependencies": { + "glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^2.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, + "glob-watcher": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", + "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", + "dev": true, + "requires": { + "gaze": "^0.5.1" + } + }, + "glob2base": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", + "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", + "dev": true, + "requires": { + "find-index": "^0.1.1" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", + "dev": true, + "requires": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + } + }, + "global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "globule": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", + "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", + "dev": true, + "requires": { + "glob": "~3.1.21", + "lodash": "~1.0.1", + "minimatch": "~0.2.11" + }, + "dependencies": { + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "~1.2.0", + "inherits": "1", + "minimatch": "~0.2.11" + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "lodash": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "glogg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", + "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "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" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "gulp": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", + "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", + "dev": true, + "requires": { + "archy": "^1.0.0", + "chalk": "^1.0.0", + "deprecated": "^0.0.1", + "gulp-util": "^3.0.0", + "interpret": "^1.0.0", + "liftoff": "^2.1.0", + "minimist": "^1.1.0", + "orchestrator": "^0.3.0", + "pretty-hrtime": "^1.0.0", + "semver": "^4.1.0", + "tildify": "^1.0.0", + "v8flags": "^2.0.2", + "vinyl-fs": "^0.3.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + } + } + }, + "gulp-help": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/gulp-help/-/gulp-help-1.6.1.tgz", + "integrity": "sha1-Jh2xhuGDl/7z9qLCLpwxW/qIrgw=", + "dev": true, + "requires": { + "chalk": "^1.0.0", + "object-assign": "^3.0.0" + }, + "dependencies": { + "object-assign": { + "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", "dev": true @@ -3937,24 +4646,24 @@ "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", "dev": true, "requires": { - "array-differ": "1.0.0", - "array-uniq": "1.0.3", - "beeper": "1.1.1", - "chalk": "1.1.3", - "dateformat": "2.2.0", - "fancy-log": "1.3.0", - "gulplog": "1.0.0", - "has-gulplog": "0.1.0", - "lodash._reescape": "3.0.0", - "lodash._reevaluate": "3.0.0", - "lodash._reinterpolate": "3.0.0", - "lodash.template": "3.6.2", - "minimist": "1.2.0", - "multipipe": "0.1.2", - "object-assign": "3.0.0", + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", "replace-ext": "0.0.1", - "through2": "2.0.3", - "vinyl": "0.5.3" + "through2": "^2.0.0", + "vinyl": "^0.5.0" }, "dependencies": { "minimist": { @@ -3977,7 +4686,7 @@ "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", "dev": true, "requires": { - "glogg": "1.0.0" + "glogg": "^1.0.0" } }, "handlebars": { @@ -3986,10 +4695,10 @@ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" }, "dependencies": { "source-map": { @@ -3998,7 +4707,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -4013,19 +4722,19 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "6.6.1", - "har-schema": "2.0.0" + "ajv": "^6.5.5", + "har-schema": "^2.0.0" }, "dependencies": { "ajv": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", - "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } } } @@ -4036,7 +4745,7 @@ "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.0.2" } }, "has-ansi": { @@ -4044,7 +4753,7 @@ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-flag": { @@ -4059,40 +4768,100 @@ "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", "dev": true, "requires": { - "sparkles": "1.0.0" + "sparkles": "^1.0.0" } }, - "hashring": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/hashring/-/hashring-3.2.0.tgz", - "integrity": "sha1-/aTv3oqiLNuX+x0qZeiEAeHBRM4=", + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, "requires": { - "connection-parse": "0.0.7", - "simple-lru-cache": "0.0.2" + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } } }, - "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hashring": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/hashring/-/hashring-3.2.0.tgz", + "integrity": "sha1-/aTv3oqiLNuX+x0qZeiEAeHBRM4=", + "requires": { + "connection-parse": "0.0.x", + "simple-lru-cache": "0.0.x" + } + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", "dev": true, "requires": { - "parse-passwd": "1.0.0" + "parse-passwd": "^1.0.0" } }, "hosted-git-info": { @@ -4114,7 +4883,7 @@ "depd": "1.1.1", "inherits": "2.0.3", "setprototypeof": "1.0.3", - "statuses": "1.4.0" + "statuses": ">= 1.3.1 < 2" } }, "http-signature": { @@ -4122,9 +4891,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.4.1", - "sshpk": "1.15.2" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "iconv-lite": { @@ -4137,11 +4906,11 @@ "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.2.0.tgz", "integrity": "sha512-8jmmFHwdPz8L73zGNAXHHOV9yXNC+Z0TUBN5rafpoaFaLFltlIFr1JkQa3FYAETP23eSsulVw0sBiwrE8jqbUg==", "requires": { - "base64-js": "1.2.1", - "crypto-js": "3.1.9-1", - "jsbn": "0.1.1", - "superagent": "3.8.3", - "url-join": "1.1.0" + "base64-js": "^1.2.0", + "crypto-js": "^3.1.9-1", + "jsbn": "^0.1.0", + "superagent": "^3.8.2", + "url-join": "^1.1.0" }, "dependencies": { "debug": { @@ -4149,7 +4918,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" } }, "formidable": { @@ -4174,24 +4943,24 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "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.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "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.1" + "safe-buffer": "~5.1.0" } }, "superagent": { @@ -4199,16 +4968,16 @@ "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.1", - "debug": "3.2.6", - "extend": "3.0.1", - "form-data": "2.3.1", - "formidable": "1.2.1", - "methods": "1.1.2", - "mime": "1.4.1", - "qs": "6.5.1", - "readable-stream": "2.3.6" + "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" } }, "url-join": { @@ -4257,8 +5026,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -4278,19 +5047,19 @@ "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", "dev": true, "requires": { - "ansi-escapes": "1.4.0", - "ansi-regex": "2.1.1", - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "cli-width": "2.2.0", - "figures": "1.7.0", - "lodash": "4.17.4", - "readline2": "1.0.1", - "run-async": "0.1.0", - "rx-lite": "3.1.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "through": "2.3.8" + "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" } }, "interpret": { @@ -4305,7 +5074,7 @@ "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", "dev": true, "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "invert-kv": { @@ -4325,8 +5094,17 @@ "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", "dev": true, "requires": { - "is-relative": "0.2.1", - "is-windows": "0.2.0" + "is-relative": "^0.2.1", + "is-windows": "^0.2.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" } }, "is-arrayish": { @@ -4341,13 +5119,14 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "1.10.0" + "binary-extensions": "^1.0.0" } }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true }, "is-builtin-module": { "version": "1.0.0", @@ -4355,7 +5134,44 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" + } + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } } }, "is-dotfile": { @@ -4370,7 +5186,7 @@ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "requires": { - "is-primitive": "2.0.0" + "is-primitive": "^2.0.0" } }, "is-extendable": { @@ -4391,7 +5207,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { @@ -4400,7 +5216,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-glob": { @@ -4409,7 +5225,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "is-installed-globally": { @@ -4418,8 +5234,8 @@ "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "dev": true, "requires": { - "global-dirs": "0.1.0", - "is-path-inside": "1.0.0" + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" } }, "is-my-json-valid": { @@ -4428,10 +5244,10 @@ "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", "dev": true, "requires": { - "generate-function": "2.0.0", - "generate-object-property": "1.2.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" } }, "is-npm": { @@ -4446,7 +5262,7 @@ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-obj": { @@ -4467,7 +5283,7 @@ "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", "dev": true, "requires": { - "is-path-inside": "1.0.0" + "is-path-inside": "^1.0.0" } }, "is-path-inside": { @@ -4476,7 +5292,7 @@ "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", "dev": true, "requires": { - "path-is-inside": "1.0.2" + "path-is-inside": "^1.0.1" } }, "is-plain-object": { @@ -4485,7 +5301,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" }, "dependencies": { "isobject": { @@ -4532,7 +5348,7 @@ "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", "dev": true, "requires": { - "is-unc-path": "0.1.2" + "is-unc-path": "^0.1.1" } }, "is-resolvable": { @@ -4541,7 +5357,7 @@ "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", "dev": true, "requires": { - "tryit": "1.0.3" + "tryit": "^1.0.1" } }, "is-retry-allowed": { @@ -4567,7 +5383,7 @@ "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", "dev": true, "requires": { - "unc-path-regex": "0.1.2" + "unc-path-regex": "^0.1.0" } }, "is-utf8": { @@ -4626,153 +5442,163 @@ "integrity": "sha1-BglrwI6Yuq10Sq5Gli2N+frGPQg=", "dev": true, "requires": { - "abbrev": "1.0.9", - "async": "1.5.2", - "istanbul-api": "1.2.1", - "js-yaml": "3.10.0", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "which": "1.3.0", - "wordwrap": "1.0.0" + "abbrev": "1.0.x", + "async": "1.x", + "istanbul-api": "^1.0.0-alpha", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "which": "^1.1.1", + "wordwrap": "^1.0.0" } }, "istanbul-api": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.2.1.tgz", - "integrity": "sha512-oFCwXvd65amgaPCzqrR+a2XjanS1MvpXN6l/MlMUTv6uiA1NOgGX+I0uyq8Lg3GDxsxPsaP1049krz3hIJ5+KA==", - "dev": true, - "requires": { - "async": "2.5.0", - "fileset": "2.0.3", - "istanbul-lib-coverage": "1.1.1", - "istanbul-lib-hook": "1.1.0", - "istanbul-lib-instrument": "1.9.1", - "istanbul-lib-report": "1.1.2", - "istanbul-lib-source-maps": "1.2.2", - "istanbul-reports": "1.1.3", - "js-yaml": "3.10.0", - "mkdirp": "0.5.1", - "once": "1.4.0" + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", + "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" }, "dependencies": { "async": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", - "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", "dev": true, "requires": { - "lodash": "4.17.4" + "lodash": "^4.17.11" } - } - } - }, - "istanbul-lib-coverage": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz", - "integrity": "sha512-0+1vDkmzxqJIn5rcoEqapSB4DmPxE31EtI2dF2aCkV5esN9EWHxZ0dwgDClivMXJqE7zaYQxq30hj5L0nlTN5Q==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz", - "integrity": "sha512-U3qEgwVDUerZ0bt8cfl3dSP3S6opBoOtk3ROO5f2EfBr/SRiD9FQqzwaZBqFORu8W7O0EXpai+k7kxHK13beRg==", - "dev": true, - "requires": { - "append-transform": "0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz", - "integrity": "sha512-RQmXeQ7sphar7k7O1wTNzVczF9igKpaeGQAG9qR2L+BS4DCJNTI9nytRmIVYevwO0bbq+2CXvJmYDuz0gMrywA==", - "dev": true, - "requires": { - "babel-generator": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "istanbul-lib-coverage": "1.1.1", - "semver": "5.4.1" - } - }, - "istanbul-lib-report": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz", - "integrity": "sha512-UTv4VGx+HZivJQwAo1wnRwe1KTvFpfi/NYwN7DcsrdzMXwpRT/Yb6r4SBPoHWj4VuQPakR32g4PUUeyKkdDkBA==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "1.1.1", - "mkdirp": "0.5.1", - "path-parse": "1.0.5", - "supports-color": "3.2.3" - }, - "dependencies": { - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "has-flag": "1.0.0" + "ms": "^2.1.1" } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz", - "integrity": "sha512-8BfdqSfEdtip7/wo1RnrvLpHVEd8zMZEDmOFEnpC6dg0vXflHt9nvoAyQUzig2uMSXfF2OBEYBV3CVjIL9JvaQ==", - "dev": true, - "requires": { - "debug": "3.1.0", - "istanbul-lib-coverage": "1.1.1", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "source-map": "0.5.7" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { - "ms": "2.0.0" + "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" } }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", + "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", + "dev": true, + "requires": { + "append-transform": "^0.4.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", + "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-source-maps": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", + "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" + } + }, + "istanbul-reports": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", + "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "handlebars": "^4.0.3" } }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "glob": "7.1.2" + "has-flag": "^1.0.0" } } } }, - "istanbul-reports": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.1.3.tgz", - "integrity": "sha512-ZEelkHh8hrZNI5xDaKwPMFwDsUf5wIEI2bXAFGp1e6deR2mnEKBPhLJEgr4ZBt8Gi6Mj38E/C8kcy9XLggVO2Q==", - "dev": true, - "requires": { - "handlebars": "4.0.11" + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "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" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + } } }, "jade": { @@ -4809,10 +5635,10 @@ "resolved": "https://registry.npmjs.org/joi/-/joi-8.4.2.tgz", "integrity": "sha1-vXd0ZY/pkFjYmU7R1LmWJITruFk=", "requires": { - "hoek": "4.2.0", - "isemail": "2.2.1", - "moment": "2.22.2", - "topo": "2.0.2" + "hoek": "4.x.x", + "isemail": "2.x.x", + "moment": "2.x.x", + "topo": "2.x.x" } }, "join-component": { @@ -4826,10 +5652,10 @@ "integrity": "sha512-6YX1g+lIl0/JDxjFFbgj7fz6i0bWFa2Hdc7PfGqFhynaEiYe1NJ3R1nda0VGaRiGU82OllR+EGDoWFpGr3k5Kg==", "dev": true, "requires": { - "config-chain": "1.1.11", - "editorconfig": "0.13.3", - "mkdirp": "0.5.1", - "nopt": "3.0.6" + "config-chain": "~1.1.5", + "editorconfig": "^0.13.2", + "mkdirp": "~0.5.0", + "nopt": "~3.0.1" } }, "js-cookie": { @@ -4849,13 +5675,13 @@ "dev": true }, "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "jsbn": { @@ -4885,7 +5711,7 @@ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stringify-safe": { @@ -4904,7 +5730,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "jsonify": { @@ -4924,15 +5750,15 @@ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", "requires": { - "jws": "3.1.5", - "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.1" + "jws": "^3.1.5", + "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" }, "dependencies": { "ms": { @@ -4960,20 +5786,76 @@ "requires": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.10", - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "jwks-rsa": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.3.0.tgz", - "integrity": "sha512-9q+d5VffK/FvFAjuXoddrq7zQybFSINV4mcwJJExGKXGyjWWpTt3vsn/aX33aB0heY02LK0qSyicdtRK0gVTig==", - "requires": { - "@types/express-jwt": "0.0.34", - "debug": "2.6.9", - "limiter": "1.1.3", - "lru-memoizer": "1.12.0", - "ms": "2.0.0", - "request": "2.88.0" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.5.1.tgz", + "integrity": "sha512-FcH0mrrfS/5CDArzw7y4AIsNJ15SkAb8XTmfEnpUDmEsUf/4KfSQifff7hYEyrZuKnhisn4L9Pme4fb/ZoHKqQ==", + "requires": { + "debug": "^2.6.9", + "jsonwebtoken": "^8.5.1", + "limiter": "^1.1.4", + "lru-memoizer": "^1.12.0", + "ms": "^2.1.1", + "request": "^2.88.0" + }, + "dependencies": { + "ecdsa-sig-formatter": { + "version": "1.0.11", + "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" + } + }, + "jsonwebtoken": { + "version": "8.5.1", + "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" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "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" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, "jws": { @@ -4981,8 +5863,8 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", "requires": { - "jwa": "1.1.6", - "safe-buffer": "5.1.1" + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" } }, "kind-of": { @@ -4991,7 +5873,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } }, "latest-version": { @@ -5000,7 +5882,7 @@ "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "dev": true, "requires": { - "package-json": "4.0.1" + "package-json": "^4.0.0" } }, "lazy-ass": { @@ -5022,7 +5904,7 @@ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "le_node": { @@ -5045,7 +5927,7 @@ }, "semver": { "version": "5.1.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=" } } @@ -5056,8 +5938,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "libpq": { @@ -5066,7 +5948,7 @@ "integrity": "sha512-0TVzqkbAZZiM8JJy5sagRyXOkvU9zTBlgGX6YdzuWECobc5F81Tp6uuS+djMZrnB5YN4O/ff52hsvXYBRW2gdQ==", "requires": { "bindings": "1.2.1", - "nan": "2.11.0" + "nan": "^2.10.0" }, "dependencies": { "nan": { @@ -5082,15 +5964,15 @@ "integrity": "sha1-qY8v9nGD2Lp8+soQVIvX/wVQs4U=", "dev": true, "requires": { - "extend": "3.0.1", - "findup-sync": "0.4.3", - "fined": "1.1.0", - "flagged-respawn": "0.3.2", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.mapvalues": "4.6.0", - "rechoir": "0.6.2", - "resolve": "1.5.0" + "extend": "^3.0.0", + "findup-sync": "^0.4.2", + "fined": "^1.0.1", + "flagged-respawn": "^0.3.2", + "lodash.isplainobject": "^4.0.4", + "lodash.isstring": "^4.0.1", + "lodash.mapvalues": "^4.4.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" }, "dependencies": { "findup-sync": { @@ -5099,18 +5981,18 @@ "integrity": "sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI=", "dev": true, "requires": { - "detect-file": "0.1.0", - "is-glob": "2.0.1", - "micromatch": "2.3.11", - "resolve-dir": "0.1.1" + "detect-file": "^0.1.0", + "is-glob": "^2.0.1", + "micromatch": "^2.3.7", + "resolve-dir": "^0.1.0" } } } }, "limiter": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.3.tgz", - "integrity": "sha512-zrycnIMsLw/3ZxTbW7HCez56rcFGecWTx5OZNplzcXUUmJLmoYArC6qdJzmAN5BWiNXGcpjhF9RQ1HSv5zebEw==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.4.tgz", + "integrity": "sha512-XCpr5bElgDI65vVgstP8TWjv6/QKWm9GU5UG0Pr5sLQ3QLo8NVKsioe+Jed5/3vFOe3IQuqE7DKwTvKQkjTHvg==" }, "load-json-file": { "version": "2.0.0", @@ -5118,10 +6000,10 @@ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" } }, "locate-path": { @@ -5130,8 +6012,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" }, "dependencies": { "path-exists": { @@ -5148,25 +6030,15 @@ "integrity": "sha1-/sfervF+fDoKVeHaBCgD4l2RdF0=" }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash-compat": { "version": "3.10.2", "resolved": "https://registry.npmjs.org/lodash-compat/-/lodash-compat-3.10.2.tgz", "integrity": "sha1-xpQBKKnTD46QLNLPmf0Muk7PwYM=" }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" - } - }, "lodash._basecopy": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", @@ -5185,23 +6057,6 @@ "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", "dev": true }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", - "dev": true - }, - "lodash._createassigner": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", - "dev": true, - "requires": { - "lodash._bindcallback": "3.0.1", - "lodash._isiterateecall": "3.0.9", - "lodash.restparam": "3.6.1" - } - }, "lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", @@ -5238,40 +6093,19 @@ "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", "dev": true }, - "lodash.assign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", - "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", - "dev": true, - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._createassigner": "3.1.1", - "lodash.keys": "3.1.2" - } - }, "lodash.cond": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", "dev": true }, - "lodash.defaults": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-3.1.2.tgz", - "integrity": "sha1-xzCLGNv4vJNy1wGnNJPGEZK9Liw=", - "dev": true, - "requires": { - "lodash.assign": "3.2.0", - "lodash.restparam": "3.6.1" - } - }, "lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", "dev": true, "requires": { - "lodash._root": "3.0.1" + "lodash._root": "^3.0.0" } }, "lodash.includes": { @@ -5322,9 +6156,9 @@ "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", "dev": true, "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" } }, "lodash.mapvalues": { @@ -5350,15 +6184,15 @@ "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", "dev": true, "requires": { - "lodash._basecopy": "3.0.1", - "lodash._basetostring": "3.0.1", - "lodash._basevalues": "3.0.0", - "lodash._isiterateecall": "3.0.9", - "lodash._reinterpolate": "3.0.0", - "lodash.escape": "3.2.0", - "lodash.keys": "3.1.2", - "lodash.restparam": "3.6.1", - "lodash.templatesettings": "3.1.1" + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" } }, "lodash.templatesettings": { @@ -5367,8 +6201,8 @@ "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", "dev": true, "requires": { - "lodash._reinterpolate": "3.0.0", - "lodash.escape": "3.2.0" + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" } }, "lolex": { @@ -5378,9 +6212,9 @@ "dev": true }, "long": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "longest": { "version": "1.0.1", @@ -5394,13 +6228,13 @@ "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "dev": true, "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0" } }, "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, "lru-cache": { @@ -5414,19 +6248,19 @@ "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-1.12.0.tgz", "integrity": "sha1-7+ZXBsyKnMZT+A8NWm6jitlQ41I=", "requires": { - "lock": "0.1.4", - "lodash": "4.17.4", - "lru-cache": "4.0.2", - "very-fast-args": "1.1.0" + "lock": "~0.1.2", + "lodash": "^4.17.4", + "lru-cache": "~4.0.0", + "very-fast-args": "^1.1.0" }, "dependencies": { "lru-cache": { "version": "4.0.2", - "resolved": "http://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" } } } @@ -5437,16 +6271,16 @@ "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", "dev": true, "requires": { - "es5-ext": "0.10.35" + "es5-ext": "~0.10.2" } }, "make-dir": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz", - "integrity": "sha512-0Pkui4wLJ7rxvmfUvs87skoEaxmu0hCUApF8nonzpl7q//FWp9zu8W61Scz4sd/kUiqDxvUhtoam2efDyiBzcA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" }, "dependencies": { "pify": { @@ -5463,10 +6297,19 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", "dev": true }, "media-typer": { @@ -5480,7 +6323,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "1.1.0" + "mimic-fn": "^1.0.0" } }, "memoizee": { @@ -5489,14 +6332,14 @@ "integrity": "sha1-vemBdmPJ5A/bKk6hw2cpYIeujI8=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.35", - "es6-weak-map": "2.0.2", - "event-emitter": "0.3.5", - "is-promise": "2.1.0", - "lru-queue": "0.1.0", - "next-tick": "1.0.0", - "timers-ext": "0.1.2" + "d": "1", + "es5-ext": "^0.10.30", + "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.2" } }, "memwatch-next": { @@ -5504,8 +6347,8 @@ "resolved": "https://registry.npmjs.org/memwatch-next/-/memwatch-next-0.3.0.tgz", "integrity": "sha1-IREFD5qQbgqi1ypOwPAInHhyb48=", "requires": { - "bindings": "1.2.1", - "nan": "2.7.0" + "bindings": "^1.2.1", + "nan": "^2.3.2" } }, "merge-descriptors": { @@ -5519,9 +6362,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.2", + "vary": "~1.1.2" } }, "methods": { @@ -5535,19 +6378,19 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "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" + "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" } }, "millisecond": { @@ -5570,7 +6413,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "requires": { - "mime-db": "1.30.0" + "mime-db": "~1.30.0" } }, "mimic-fn": { @@ -5584,7 +6427,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -5592,6 +6435,27 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -5624,195 +6488,686 @@ "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", "dev": true }, - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true + } + } + }, + "moment": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + }, + "moment-timezone": { + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", + "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", + "requires": { + "moment": ">= 2.9.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + } + }, + "murmur-hash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmur-hash-js/-/murmur-hash-js-1.0.0.tgz", + "integrity": "sha1-UEEEkmnJZjPIZjhpYLL0KJ515bA=" + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + } + }, + "nan": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "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" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "natives": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.0.tgz", + "integrity": "sha1-6f+EFBimsux6SV6TmYT3jxY+bjE=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nice-simple-logger": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nice-simple-logger/-/nice-simple-logger-1.0.1.tgz", + "integrity": "sha1-D55khSe+e+PkmrdvqMjAmK+VG/Y=", + "requires": { + "lodash": "^4.3.0" + } + }, + "no-kafka": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/no-kafka/-/no-kafka-3.4.3.tgz", + "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" + } + }, + "nodemon": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", + "integrity": "sha512-/DXLzd/GhiaDXXbGId5BzxP1GlsqtMGM9zTmkWrgXtSqjKmGSbLicM/oAy4FR0YWm14jCHRwnR31AHS2dYFHrg==", + "dev": true, + "requires": { + "chokidar": "^2.1.5", + "debug": "^3.1.0", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.6", + "semver": "^5.5.0", + "supports-color": "^5.2.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.5.0" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "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" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "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" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "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" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "ms": "0.7.1" + "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" } }, - "escape-string-regexp": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", - "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "inherits": "2.0.3", - "minimatch": "0.3.0" + "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" } }, - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "supports-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", - "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", - "dev": true - } - } - }, - "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" - }, - "moment-timezone": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", - "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", - "requires": { - "moment": "2.22.2" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "requires": { - "duplexer2": "0.0.2" - } - }, - "murmur-hash-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/murmur-hash-js/-/murmur-hash-js-1.0.0.tgz", - "integrity": "sha1-UEEEkmnJZjPIZjhpYLL0KJ515bA=" - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "ncp": "2.0.0", - "rimraf": "2.4.5" - } - }, - "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" - }, - "natives": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.0.tgz", - "integrity": "sha1-6f+EFBimsux6SV6TmYT3jxY+bjE=", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", - "optional": true - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "nice-simple-logger": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nice-simple-logger/-/nice-simple-logger-1.0.1.tgz", - "integrity": "sha1-D55khSe+e+PkmrdvqMjAmK+VG/Y=", - "requires": { - "lodash": "4.17.4" - } - }, - "no-kafka": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/no-kafka/-/no-kafka-3.2.10.tgz", - "integrity": "sha1-0sq8QwZbSS24wVyiOK6V8WgIGvU=", - "requires": { - "@types/bluebird": "3.5.0", - "@types/lodash": "4.14.116", - "bin-protocol": "3.0.4", - "bluebird": "3.5.1", - "buffer-crc32": "0.2.13", - "hashring": "3.2.0", - "lodash": "4.17.5", - "murmur-hash-js": "1.0.0", - "nice-simple-logger": "1.0.1", - "wrr-pool": "1.1.3" - }, - "dependencies": { - "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "nodemon": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.12.1.tgz", - "integrity": "sha1-mWpW3EnZ8Wu/G3ik3gjxNjSzh40=", - "dev": true, - "requires": { - "chokidar": "1.7.0", - "debug": "2.6.9", - "es6-promise": "3.3.1", - "ignore-by-default": "1.0.1", - "lodash.defaults": "3.1.2", - "minimatch": "3.0.4", - "ps-tree": "1.1.0", - "touch": "3.1.0", - "undefsafe": "0.0.3", - "update-notifier": "2.3.0" - } - }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.0.9" + "abbrev": "1" } }, "normalize-package-data": { @@ -5821,10 +7176,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.4.1", - "validate-npm-package-license": "3.0.1" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { @@ -5833,7 +7188,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "1.1.0" + "remove-trailing-separator": "^1.0.1" } }, "npm-run-path": { @@ -5842,7 +7197,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "number-is-nan": { @@ -5861,16 +7216,55 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", "dev": true, "requires": { - "array-each": "1.0.1", - "array-slice": "1.0.0", - "for-own": "1.0.0", - "isobject": "3.0.1" + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" }, "dependencies": { "for-own": { @@ -5879,7 +7273,7 @@ "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", "dev": true, "requires": { - "for-in": "1.0.2" + "for-in": "^1.0.1" } }, "isobject": { @@ -5896,8 +7290,8 @@ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" } }, "object.pick": { @@ -5906,7 +7300,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" }, "dependencies": { "isobject": { @@ -5935,7 +7329,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "onetime": { @@ -5950,8 +7344,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.3" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" }, "dependencies": { "wordwrap": { @@ -5968,12 +7362,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" } }, "orchestrator": { @@ -5982,9 +7376,9 @@ "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", "dev": true, "requires": { - "end-of-stream": "0.1.5", - "sequencify": "0.0.7", - "stream-consume": "0.1.0" + "end-of-stream": "~0.1.5", + "sequencify": "~0.0.7", + "stream-consume": "~0.1.0" } }, "ordered-read-streams": { @@ -6004,9 +7398,9 @@ "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "dev": true, "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" } }, "os-tmpdir": { @@ -6021,9 +7415,9 @@ "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "mkdirp": "0.5.1", - "object-assign": "4.1.1" + "graceful-fs": "^4.1.4", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.0" } }, "p-finally": { @@ -6044,7 +7438,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.1.0" + "p-limit": "^1.1.0" } }, "package-json": { @@ -6053,10 +7447,10 @@ "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "dev": true, "requires": { - "got": "6.7.1", - "registry-auth-token": "3.3.1", - "registry-url": "3.1.0", - "semver": "5.4.1" + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" } }, "packet-reader": { @@ -6070,9 +7464,9 @@ "integrity": "sha1-FZ1hVdQ5BNFsEO9piRHaHpGWm3M=", "dev": true, "requires": { - "is-absolute": "0.2.6", - "map-cache": "0.2.2", - "path-root": "0.1.1" + "is-absolute": "^0.2.3", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" } }, "parse-glob": { @@ -6081,10 +7475,10 @@ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" } }, "parse-json": { @@ -6093,7 +7487,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "^1.2.0" } }, "parse-passwd": { @@ -6107,13 +7501,25 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-is-absolute": { @@ -6145,7 +7551,7 @@ "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", "dev": true, "requires": { - "path-root-regex": "0.1.2" + "path-root-regex": "^0.1.0" } }, "path-root-regex": { @@ -6165,16 +7571,7 @@ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "pify": "2.3.0" - } - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "2.3.8" + "pify": "^2.0.0" } }, "performance-now": { @@ -6192,9 +7589,9 @@ "js-string-escape": "1.0.1", "packet-reader": "0.2.0", "pg-connection-string": "0.1.3", - "pg-types": "1.12.1", + "pg-types": "1.*", "pgpass": "0.0.3", - "semver": "4.3.6" + "semver": "^4.1.0" }, "dependencies": { "semver": { @@ -6214,7 +7611,7 @@ "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-1.10.1.tgz", "integrity": "sha1-lOYcy7hafzQ2suUmMVx1gRB/5Aw=", "requires": { - "libpq": "1.8.8", + "libpq": "^1.7.0", "pg-types": "1.6.0", "readable-stream": "1.0.31" }, @@ -6229,10 +7626,10 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.31.tgz", "integrity": "sha1-jyUC4LyeOw2huUUgqrtOJgPsr64=", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } } } @@ -6242,10 +7639,10 @@ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz", "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=", "requires": { - "postgres-array": "1.0.2", - "postgres-bytea": "1.0.0", - "postgres-date": "1.0.3", - "postgres-interval": "1.1.1" + "postgres-array": "~1.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.0", + "postgres-interval": "^1.1.0" } }, "pgpass": { @@ -6253,7 +7650,7 @@ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-0.0.3.tgz", "integrity": "sha1-EuZ+NDsxicLzEgbrycwL7//PkUA=", "requires": { - "split": "0.3.3" + "split": "~0.3" } }, "pify": { @@ -6274,7 +7671,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "pkg-dir": { @@ -6283,7 +7680,7 @@ "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", "dev": true, "requires": { - "find-up": "1.1.2" + "find-up": "^1.0.0" } }, "pluralize": { @@ -6292,6 +7689,12 @@ "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", "dev": true }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, "postgres-array": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", @@ -6312,7 +7715,7 @@ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz", "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==", "requires": { - "xtend": "4.0.1" + "xtend": "^4.0.0" } }, "precond": { @@ -6366,7 +7769,7 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "requires": { - "asap": "2.0.6" + "asap": "~2.0.3" } }, "proto-list": { @@ -6385,28 +7788,25 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", "requires": { - "forwarded": "0.1.2", + "forwarded": "~0.1.2", "ipaddr.js": "1.5.2" } }, - "ps-tree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", - "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", - "dev": true, - "requires": { - "event-stream": "3.3.4" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + "version": "1.1.32", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", + "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" + }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", + "dev": true }, "punycode": { "version": "1.3.2", @@ -6423,47 +7823,6 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, - "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -6481,15 +7840,15 @@ } }, "rc": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz", - "integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -6506,9 +7865,9 @@ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" } }, "read-pkg-up": { @@ -6517,8 +7876,8 @@ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" }, "dependencies": { "find-up": { @@ -6527,7 +7886,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } } } @@ -6537,10 +7896,10 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "readdirp": { @@ -6548,32 +7907,35 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, + "optional": true, "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.3", - "set-immediate-shim": "1.0.1" + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" }, "dependencies": { "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, + "optional": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -6581,8 +7943,9 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, + "optional": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -6593,8 +7956,8 @@ "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", "mute-stream": "0.0.5" } }, @@ -6614,7 +7977,7 @@ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "resolve": "1.5.0" + "resolve": "^1.1.6" } }, "reconnect-core": { @@ -6622,7 +7985,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" } }, "redefine": { @@ -6649,9 +8012,9 @@ "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "private": "0.1.8" + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" }, "dependencies": { "babel-runtime": { @@ -6660,8 +8023,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", - "regenerator-runtime": "0.11.0" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -6672,7 +8035,17 @@ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { - "is-equal-shallow": "0.1.3" + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" } }, "regexpu-core": { @@ -6681,19 +8054,19 @@ "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", "dev": true, "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" } }, "registry-auth-token": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", - "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "dev": true, "requires": { - "rc": "1.2.2", - "safe-buffer": "5.1.1" + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" } }, "registry-url": { @@ -6702,7 +8075,7 @@ "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "dev": true, "requires": { - "rc": "1.2.2" + "rc": "^1.0.1" } }, "regjsgen": { @@ -6717,7 +8090,7 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { - "jsesc": "0.5.0" + "jsesc": "~0.5.0" }, "dependencies": { "jsesc": { @@ -6757,7 +8130,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "1.0.2" + "is-finite": "^1.0.0" } }, "replace-ext": { @@ -6771,34 +8144,34 @@ "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.7", - "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.21", - "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.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" }, "dependencies": { "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "version": "1.0.8", + "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" } }, "extend": { @@ -6811,22 +8184,22 @@ "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.7", - "mime-types": "2.1.21" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" } }, "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "requires": { - "mime-db": "1.37.0" + "mime-db": "1.40.0" } }, "qs": { @@ -6859,8 +8232,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" } }, "resolve": { @@ -6869,7 +8242,7 @@ "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", "dev": true, "requires": { - "path-parse": "1.0.5" + "path-parse": "^1.0.5" } }, "resolve-dir": { @@ -6878,8 +8251,8 @@ "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", "dev": true, "requires": { - "expand-tilde": "1.2.2", - "global-modules": "0.2.3" + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" } }, "resolve-from": { @@ -6888,23 +8261,35 @@ "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", "dev": true }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, "restore-cursor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", "dev": true, "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" } }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, "retry-as-promised": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", "requires": { - "bluebird": "3.5.1", - "debug": "2.6.9" + "bluebird": "^3.4.6", + "debug": "^2.6.9" } }, "right-align": { @@ -6914,7 +8299,7 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4" + "align-text": "^0.1.1" } }, "rimraf": { @@ -6922,7 +8307,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", "requires": { - "glob": "6.0.4" + "glob": "^6.0.1" } }, "run-async": { @@ -6931,7 +8316,7 @@ "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.3.0" } }, "rx-lite": { @@ -6951,6 +8336,15 @@ "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=", "optional": true }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -6983,7 +8377,7 @@ "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "dev": true, "requires": { - "semver": "5.4.1" + "semver": "^5.0.3" } }, "send": { @@ -6992,18 +8386,18 @@ "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", "requires": { "debug": "2.6.9", - "depd": "1.1.1", - "destroy": "1.0.4", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.1", + "destroy": "~1.0.4", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.6.2", + "http-errors": "~1.6.2", "mime": "1.4.1", "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.3.1" }, "dependencies": { "statuses": { @@ -7018,21 +8412,21 @@ "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-3.30.4.tgz", "integrity": "sha1-vaLfHjGFSwmeQUmhEen8Clyh0aQ=", "requires": { - "bluebird": "3.5.1", - "depd": "1.1.1", - "dottie": "1.1.1", + "bluebird": "^3.3.4", + "depd": "^1.1.0", + "dottie": "^1.0.0", "generic-pool": "2.4.2", - "inflection": "1.12.0", + "inflection": "^1.6.0", "lodash": "4.12.0", - "moment": "2.22.2", - "moment-timezone": "0.5.14", - "retry-as-promised": "2.3.2", - "semver": "5.4.1", + "moment": "^2.13.0", + "moment-timezone": "^0.5.4", + "retry-as-promised": "^2.0.0", + "semver": "^5.0.1", "shimmer": "1.1.0", - "terraformer-wkt-parser": "1.1.2", - "toposort-class": "1.0.1", - "uuid": "3.3.2", - "validator": "5.7.0", + "terraformer-wkt-parser": "^1.1.0", + "toposort-class": "^1.0.1", + "uuid": "^3.0.0", + "validator": "^5.2.0", "wkx": "0.2.0" }, "dependencies": { @@ -7049,18 +8443,18 @@ "integrity": "sha1-QwTM5g5JkWlgP4ON7bq0IcmEnnQ=", "dev": true, "requires": { - "bluebird": "3.5.1", - "cli-color": "1.2.0", - "findup-sync": "1.0.0", - "fs-extra": "4.0.2", - "gulp": "3.9.1", - "gulp-help": "1.6.1", - "js-beautify": "1.7.4", - "lodash": "4.17.4", - "moment": "2.22.2", - "resolve": "1.5.0", - "umzug": "1.12.0", - "yargs": "8.0.2" + "bluebird": "^3.5.0", + "cli-color": "~1.2.0", + "findup-sync": "^1.0.0", + "fs-extra": "^4.0.1", + "gulp": "^3.9.1", + "gulp-help": "~1.6.1", + "js-beautify": "^1.6.11", + "lodash": "^4.17.4", + "moment": "^2.17.1", + "resolve": "^1.3.3", + "umzug": "^1.12.0", + "yargs": "^8.0.1" }, "dependencies": { "ansi-regex": { @@ -7081,9 +8475,9 @@ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" }, "dependencies": { "string-width": { @@ -7092,9 +8486,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -7105,8 +8499,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" }, "dependencies": { "is-fullwidth-code-point": { @@ -7121,7 +8515,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -7132,19 +8526,19 @@ "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", "dev": true, "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" } } } @@ -7160,9 +8554,9 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", "requires": { - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "parseurl": "1.3.2", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", "send": "0.16.1" } }, @@ -7176,7 +8570,31 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true + "dev": true, + "optional": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } }, "setprototypeof": { "version": "1.0.3", @@ -7189,7 +8607,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -7204,9 +8622,9 @@ "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", "dev": true, "requires": { - "glob": "7.1.2", - "interpret": "1.0.4", - "rechoir": "0.6.2" + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" }, "dependencies": { "glob": { @@ -7215,12 +8633,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "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" } } } @@ -7256,7 +8674,7 @@ "formatio": "1.1.1", "lolex": "1.3.2", "samsam": "1.1.2", - "util": "0.10.3" + "util": ">=0.10.3 <1" } }, "sinon-chai": { @@ -7277,7 +8695,7 @@ "integrity": "sha1-h4+h1E0I7rDyb7IBjvhinrGjq5Q=", "dev": true, "requires": { - "nan": "2.7.0" + "nan": ">=2.5.1" } }, "slice-ansi": { @@ -7286,21 +8704,148 @@ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, "source-map-support": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "source-map": "0.5.7" + "source-map": "^0.5.6" } }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, "sparkles": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", @@ -7313,7 +8858,7 @@ "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", "dev": true, "requires": { - "spdx-license-ids": "1.2.2" + "spdx-license-ids": "^1.0.2" } }, "spdx-expression-parse": { @@ -7333,7 +8878,16 @@ "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "requires": { - "through": "2.3.8" + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" } }, "sprintf-js": { @@ -7343,19 +8897,40 @@ "dev": true }, "sshpk": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", - "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "version": "1.16.1", + "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" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, "requires": { - "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" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } } }, "statuses": { @@ -7363,15 +8938,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "0.1.1" - } - }, "stream-consume": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz", @@ -7383,9 +8949,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { @@ -7398,7 +8964,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -7424,16 +8990,16 @@ "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.0.tgz", "integrity": "sha512-71XGWgtn70TNwgmgYa69dPOYg55aU9FCahjUNY03rOrKvaTCaU3b9MeZmqonmf9Od96SCxr3vGfEAnhM7dtxCw==", "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.1", - "debug": "3.1.0", - "extend": "3.0.1", - "form-data": "2.3.1", - "formidable": "1.1.1", - "methods": "1.1.2", - "mime": "1.4.1", - "qs": "6.5.1", - "readable-stream": "2.3.3" + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.1.1", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.0.5" }, "dependencies": { "debug": { @@ -7454,13 +9020,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -7468,7 +9034,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -7484,8 +9050,8 @@ "integrity": "sha1-oFgIHXiPFRXUcA11Aogea3WeRM0=", "dev": true, "requires": { - "methods": "1.1.2", - "superagent": "2.3.0" + "methods": "1.x", + "superagent": "^2.0.0" }, "dependencies": { "form-data": { @@ -7494,9 +9060,9 @@ "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=", "dev": true, "requires": { - "async": "1.5.2", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "async": "^1.5.2", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.10" } }, "isarray": { @@ -7511,13 +9077,13 @@ "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -7526,7 +9092,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, "superagent": { @@ -7535,16 +9101,16 @@ "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=", "dev": true, "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1", + "component-emitter": "^1.2.0", + "cookiejar": "^2.0.6", + "debug": "^2.2.0", + "extend": "^3.0.0", "form-data": "1.0.0-rc4", - "formidable": "1.1.1", - "methods": "1.1.2", - "mime": "1.4.1", - "qs": "6.5.1", - "readable-stream": "2.3.3" + "formidable": "^1.0.17", + "methods": "^1.1.1", + "mime": "^1.3.4", + "qs": "^6.1.0", + "readable-stream": "^2.0.5" } } } @@ -7560,12 +9126,12 @@ "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", "dev": true, "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.4", + "ajv": "^4.7.0", + "ajv-keywords": "^1.0.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", "slice-ansi": "0.0.4", - "string-width": "2.1.1" + "string-width": "^2.0.0" }, "dependencies": { "ansi-regex": { @@ -7586,8 +9152,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": { @@ -7596,23 +9162,24 @@ "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#d16413db30b1eed21c0cf426e185bedb2329ddab", - "requires": { - "auth0-js": "9.8.2", - "axios": "0.12.0", - "bunyan": "1.8.12", - "jsonwebtoken": "8.3.0", - "jwks-rsa": "1.3.0", - "le_node": "1.8.0", - "lodash": "4.17.11", - "millisecond": "0.1.2", - "request": "2.88.0" + "from": "github:appirio-tech/tc-core-library-js#v2.6", + "requires": { + "auth0-js": "^9.4.2", + "axios": "^0.12.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" }, "dependencies": { "axios": { @@ -7628,8 +9195,8 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz", "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk=", "requires": { - "debug": "2.6.9", - "stream-consume": "0.1.0" + "debug": "^2.2.0", + "stream-consume": "^0.1.0" } }, "lodash": { @@ -7645,7 +9212,7 @@ "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "dev": true, "requires": { - "execa": "0.7.0" + "execa": "^0.7.0" } }, "terraformer": { @@ -7653,7 +9220,7 @@ "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.8.tgz", "integrity": "sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM=", "requires": { - "@types/geojson": "1.0.6" + "@types/geojson": "^1.0.0" } }, "terraformer-wkt-parser": { @@ -7661,7 +9228,7 @@ "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.1.2.tgz", "integrity": "sha1-M2oMj8gglKWv+DKI9prt7NNpvww=", "requires": { - "terraformer": "1.0.8" + "terraformer": "~1.0.5" } }, "text-table": { @@ -7681,8 +9248,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "2.3.3", - "xtend": "4.0.1" + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" }, "dependencies": { "isarray": { @@ -7697,13 +9264,13 @@ "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -7712,7 +9279,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -7723,7 +9290,7 @@ "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", "dev": true, "requires": { - "os-homedir": "1.0.2" + "os-homedir": "^1.0.0" } }, "time-stamp": { @@ -7744,8 +9311,8 @@ "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=", "dev": true, "requires": { - "es5-ext": "0.10.35", - "next-tick": "1.0.0" + "es5-ext": "~0.10.14", + "next-tick": "1" } }, "to-fast-properties": { @@ -7760,12 +9327,54 @@ "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", "dev": true }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "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" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, "topo": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", "requires": { - "hoek": "4.2.0" + "hoek": "4.x.x" } }, "toposort-class": { @@ -7779,7 +9388,7 @@ "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", "dev": true, "requires": { - "nopt": "1.0.10" + "nopt": "~1.0.10" }, "dependencies": { "nopt": { @@ -7788,7 +9397,7 @@ "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, "requires": { - "abbrev": "1.0.9" + "abbrev": "1" } } } @@ -7798,8 +9407,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.31", - "punycode": "1.4.1" + "psl": "^1.1.24", + "punycode": "^1.4.1" }, "dependencies": { "punycode": { @@ -7831,7 +9440,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -7845,7 +9454,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-detect": { @@ -7860,7 +9469,7 @@ "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.17" + "mime-types": "~2.1.15" } }, "typedarray": { @@ -7876,9 +9485,9 @@ "dev": true, "optional": true, "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" } }, "uglify-to-browserify": { @@ -7894,11 +9503,11 @@ "integrity": "sha1-p5yR8oYu7jEwxsNH8rkK1opm6Lg=", "dev": true, "requires": { - "bluebird": "3.5.1", - "lodash": "4.17.4", - "moment": "2.22.2", - "redefine": "0.2.1", - "resolve": "1.5.0" + "bluebird": "^3.4.1", + "lodash": "^4.17.0", + "moment": "^2.16.0", + "redefine": "^0.2.0", + "resolve": "^1.0.0" } }, "unc-path-regex": { @@ -7908,10 +9517,48 @@ "dev": true }, "undefsafe": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-0.0.3.tgz", - "integrity": "sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } }, "unique-stream": { "version": "1.0.0", @@ -7925,7 +9572,7 @@ "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "dev": true, "requires": { - "crypto-random-string": "1.0.0" + "crypto-random-string": "^1.0.0" } }, "universalify": { @@ -7939,62 +9586,121 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "unzip-response": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", "dev": true }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, "update-notifier": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.3.0.tgz", - "integrity": "sha1-TognpruRUUCrCTVZ1wFOPruDdFE=", - "dev": true, - "requires": { - "boxen": "1.2.2", - "chalk": "2.3.0", - "configstore": "3.1.1", - "import-lazy": "2.1.0", - "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" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "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" }, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "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==", "dev": true, "requires": { - "color-convert": "1.9.0" + "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "^3.0.0" } } } @@ -8004,7 +9710,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "requires": { - "punycode": "2.1.1" + "punycode": "^2.1.0" }, "dependencies": { "punycode": { @@ -8014,6 +9720,12 @@ } } }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", @@ -8034,7 +9746,7 @@ "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, "requires": { - "prepend-http": "1.0.4" + "prepend-http": "^1.0.1" } }, "urlencode": { @@ -8042,9 +9754,15 @@ "resolved": "https://registry.npmjs.org/urlencode/-/urlencode-1.1.0.tgz", "integrity": "sha1-HyuibwE8hfATP3o61v8nMK33y7c=", "requires": { - "iconv-lite": "0.4.19" + "iconv-lite": "~0.4.11" } }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, "user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", @@ -8089,7 +9807,7 @@ "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { - "user-home": "1.1.1" + "user-home": "^1.1.1" } }, "validate-npm-package-license": { @@ -8098,8 +9816,8 @@ "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", "dev": true, "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" + "spdx-correct": "~1.0.0", + "spdx-expression-parse": "~1.0.0" } }, "validator": { @@ -8117,9 +9835,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.3.0" + "extsprintf": "^1.2.0" } }, "very-fast-args": { @@ -8133,8 +9851,8 @@ "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", "dev": true, "requires": { - "clone": "1.0.2", - "clone-stats": "0.0.1", + "clone": "^1.0.0", + "clone-stats": "^0.0.1", "replace-ext": "0.0.1" }, "dependencies": { @@ -8152,14 +9870,14 @@ "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", "dev": true, "requires": { - "defaults": "1.0.3", - "glob-stream": "3.1.18", - "glob-watcher": "0.0.6", - "graceful-fs": "3.0.11", - "mkdirp": "0.5.1", - "strip-bom": "1.0.0", - "through2": "0.6.5", - "vinyl": "0.4.6" + "defaults": "^1.0.0", + "glob-stream": "^3.1.5", + "glob-watcher": "^0.0.6", + "graceful-fs": "^3.0.0", + "mkdirp": "^0.5.0", + "strip-bom": "^1.0.0", + "through2": "^0.6.1", + "vinyl": "^0.4.0" }, "dependencies": { "clone": { @@ -8174,7 +9892,7 @@ "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", "dev": true, "requires": { - "natives": "1.1.0" + "natives": "^1.1.0" } }, "readable-stream": { @@ -8183,10 +9901,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "strip-bom": { @@ -8195,8 +9913,8 @@ "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", "dev": true, "requires": { - "first-chunk-stream": "1.0.0", - "is-utf8": "0.2.1" + "first-chunk-stream": "^1.0.0", + "is-utf8": "^0.2.0" } }, "through2": { @@ -8205,8 +9923,8 @@ "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, "requires": { - "readable-stream": "1.0.34", - "xtend": "4.0.1" + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" } }, "vinyl": { @@ -8215,8 +9933,8 @@ "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, "requires": { - "clone": "0.2.0", - "clone-stats": "0.0.1" + "clone": "^0.2.0", + "clone-stats": "^0.0.1" } } } @@ -8227,7 +9945,7 @@ "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -8237,12 +9955,45 @@ "dev": true }, "widest-line": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz", - "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { - "string-width": "1.0.2" + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "winchan": { @@ -8274,8 +10025,8 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" } }, "wrappy": { @@ -8289,26 +10040,26 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "0.5.1" + "mkdirp": "^0.5.1" } }, "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "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.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" } }, "wrr-pool": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wrr-pool/-/wrr-pool-1.1.3.tgz", - "integrity": "sha1-/a0i8uofMDY//l14HPeUl6d/8H4=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/wrr-pool/-/wrr-pool-1.1.4.tgz", + "integrity": "sha512-+lEdj42HlYqmzhvkZrx6xEymj0wzPBxqr7U1Xh9IWikMzOge03JSQT9YzTGq54SkOh/noViq32UejADZVzrgAg==", "requires": { - "lodash": "4.17.4" + "lodash": "^4.17.11" } }, "xdg-basedir": { @@ -8318,21 +10069,18 @@ "dev": true }, "xml2js": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", - "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "requires": { - "sax": "1.2.1", - "xmlbuilder": "4.2.1" + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" } }, "xmlbuilder": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", - "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", - "requires": { - "lodash": "4.17.4" - } + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "xtend": { "version": "4.0.1", @@ -8357,9 +10105,9 @@ "dev": true, "optional": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", "window-size": "0.1.0" } }, @@ -8369,7 +10117,7 @@ "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" }, "dependencies": { "camelcase": { diff --git a/package.json b/package.json index c1921857..9ec62ec8 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "amqplib": "^0.5.1", "analytics-node": "^2.1.1", "app-module-path": "^1.0.7", - "aws-sdk": "^2.33.0", - "axios": "^0.17.1", + "aws-sdk": "^2.468.0", + "axios": "^0.19.0", "bluebird": "^3.4.1", "body-parser": "^1.15.0", "co": "^4.6.0", @@ -54,11 +54,11 @@ "http-aws-es": "^4.0.0", "joi": "^8.0.5", "jsonwebtoken": "^8.3.0", - "lodash": "^4.16.4", + "lodash": "^4.17.11", "memwatch-next": "^0.3.0", "method-override": "^2.3.9", "moment": "^2.22.2", - "no-kafka": "^3.2.10", + "no-kafka": "^3.4.3", "pg": "^4.5.5", "pg-native": "^1.10.1", "sequelize": "^3.23.0", @@ -68,7 +68,7 @@ }, "devDependencies": { "babel-cli": "^6.9.0", - "babel-core": "^6.11.4", + "babel-core": "^6.26.3", "babel-eslint": "^7.1.1", "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-transform-runtime": "^6.23.0", @@ -81,7 +81,7 @@ "eslint-plugin-import": "^2.2.0", "istanbul": "^1.0.0-alpha.2", "mocha": "^2.5.3", - "nodemon": "^1.9.1", + "nodemon": "^1.19.1", "really-need": "^1.9.2", "sequelize-cli": "^2.4.0", "sinon": "^1.17.4", From e3b8eed2bbd35d9c83e2ca954f36b2a80821d1cf Mon Sep 17 00:00:00 2001 From: Sharathkumar Anbu Date: Fri, 14 Jun 2019 19:21:24 +0530 Subject: [PATCH 02/88] 30092642 - Upgrade to V5 - Part 1 --- config/custom-environment-variables.json | 2 +- config/default.json | 24 +- config/m2m.local.js | 4 +- config/mock.local.js | 4 +- config/test.json | 6 +- ...on => Project API.postman_collection.json} | 1465 +++-- ...n => Project API.postman_environment.json} | 10 +- local/seed/index.js | 2 +- migrations/elasticsearch_sync.js | 298 + package-lock.json | 5118 ++++++++--------- package.json | 28 +- src/app.js | 7 + src/constants.js | 47 +- src/events/busApi.js | 112 +- src/events/projects/index.js | 2 +- src/events/projects/index.spec.js | 2 +- src/middlewares/fieldLookupValidation.js | 2 +- src/middlewares/validateMilestoneTemplate.js | 13 +- src/middlewares/validateTimeline.js | 16 +- src/models/index.js | 16 + src/models/milestone.js | 87 +- src/models/phaseProduct.js | 56 +- src/models/project.js | 238 +- src/models/projectAttachment.js | 63 +- src/models/projectMember.js | 38 +- src/models/projectMemberInvite.js | 109 +- src/models/projectPhase.js | 81 +- src/models/timeline.js | 9 +- src/routes/attachments/create.js | 2 +- src/routes/attachments/create.spec.js | 6 +- src/routes/attachments/delete.spec.js | 14 +- src/routes/attachments/download.spec.js | 12 +- src/routes/attachments/update.js | 2 +- src/routes/attachments/update.spec.js | 10 +- src/routes/form/revision/create.js | 32 +- src/routes/form/revision/create.spec.js | 48 +- src/routes/form/revision/delete.js | 12 +- src/routes/form/revision/delete.spec.js | 35 +- src/routes/form/revision/get.js | 73 +- src/routes/form/revision/get.spec.js | 28 +- src/routes/form/revision/list.js | 47 +- src/routes/form/revision/list.spec.js | 28 +- src/routes/form/version/create.js | 34 +- src/routes/form/version/create.spec.js | 53 +- src/routes/form/version/delete.js | 18 +- src/routes/form/version/delete.spec.js | 30 +- src/routes/form/version/get.js | 57 +- src/routes/form/version/get.spec.js | 30 +- src/routes/form/version/getVersion.js | 70 +- src/routes/form/version/getVersion.spec.js | 30 +- src/routes/form/version/list.js | 62 +- src/routes/form/version/list.spec.js | 28 +- src/routes/form/version/update.js | 32 +- src/routes/form/version/update.spec.js | 44 +- src/routes/index.js | 153 +- src/routes/metadata/list.js | 9 +- src/routes/metadata/list.spec.js | 30 +- src/routes/milestoneTemplates/clone.spec.js | 40 +- src/routes/milestoneTemplates/create.js | 1 - src/routes/milestoneTemplates/create.spec.js | 54 +- src/routes/milestoneTemplates/delete.spec.js | 34 +- src/routes/milestoneTemplates/get.spec.js | 26 +- src/routes/milestoneTemplates/list.js | 2 +- src/routes/milestoneTemplates/list.spec.js | 36 +- src/routes/milestoneTemplates/update.spec.js | 79 +- src/routes/milestones/create.js | 2 +- src/routes/milestones/create.spec.js | 102 +- src/routes/milestones/delete.spec.js | 44 +- src/routes/milestones/get.spec.js | 38 +- src/routes/milestones/list.js | 2 +- src/routes/milestones/list.spec.js | 34 +- src/routes/milestones/update.js | 2 +- src/routes/milestones/update.spec.js | 168 +- src/routes/orgConfig/create.js | 46 +- src/routes/orgConfig/create.spec.js | 85 +- src/routes/orgConfig/delete.js | 11 +- src/routes/orgConfig/delete.spec.js | 43 +- src/routes/orgConfig/get.js | 58 +- src/routes/orgConfig/get.spec.js | 30 +- src/routes/orgConfig/list.js | 53 +- src/routes/orgConfig/list.spec.js | 23 +- src/routes/orgConfig/update.js | 35 +- src/routes/orgConfig/update.spec.js | 84 +- src/routes/phaseProducts/create.spec.js | 36 +- src/routes/phaseProducts/delete.spec.js | 16 +- src/routes/phaseProducts/get.spec.js | 12 +- src/routes/phaseProducts/list-db.spec.js | 10 +- src/routes/phaseProducts/list.spec.js | 10 +- src/routes/phaseProducts/update.spec.js | 28 +- src/routes/phases/create.js | 6 +- src/routes/phases/create.spec.js | 48 +- src/routes/phases/delete.spec.js | 14 +- src/routes/phases/get.spec.js | 10 +- src/routes/phases/list-db.spec.js | 8 +- src/routes/phases/list.spec.js | 8 +- src/routes/phases/update.spec.js | 40 +- src/routes/planConfig/revision/create.js | 32 +- src/routes/planConfig/revision/create.spec.js | 48 +- src/routes/planConfig/revision/delete.js | 13 +- src/routes/planConfig/revision/delete.spec.js | 34 +- src/routes/planConfig/revision/get.js | 73 +- src/routes/planConfig/revision/get.spec.js | 28 +- src/routes/planConfig/revision/list.js | 49 +- src/routes/planConfig/revision/list.spec.js | 28 +- src/routes/planConfig/version/create.js | 32 +- src/routes/planConfig/version/create.spec.js | 44 +- src/routes/planConfig/version/delete.js | 19 +- src/routes/planConfig/version/delete.spec.js | 30 +- src/routes/planConfig/version/get.js | 54 +- src/routes/planConfig/version/get.spec.js | 30 +- src/routes/planConfig/version/getVersion.js | 69 +- .../planConfig/version/getVersion.spec.js | 30 +- src/routes/planConfig/version/list.js | 64 +- src/routes/planConfig/version/list.spec.js | 28 +- src/routes/planConfig/version/update.js | 30 +- src/routes/planConfig/version/update.spec.js | 44 +- src/routes/priceConfig/revision/create.js | 32 +- .../priceConfig/revision/create.spec.js | 49 +- src/routes/priceConfig/revision/delete.js | 13 +- .../priceConfig/revision/delete.spec.js | 35 +- src/routes/priceConfig/revision/get.js | 73 +- src/routes/priceConfig/revision/get.spec.js | 28 +- src/routes/priceConfig/revision/list.js | 49 +- src/routes/priceConfig/revision/list.spec.js | 28 +- src/routes/priceConfig/version/create.js | 34 +- src/routes/priceConfig/version/create.spec.js | 44 +- src/routes/priceConfig/version/delete.js | 19 +- src/routes/priceConfig/version/delete.spec.js | 30 +- src/routes/priceConfig/version/get.js | 57 +- src/routes/priceConfig/version/get.spec.js | 30 +- src/routes/priceConfig/version/getVersion.js | 70 +- .../priceConfig/version/getVersion.spec.js | 30 +- src/routes/priceConfig/version/list.js | 62 +- src/routes/priceConfig/version/list.spec.js | 28 +- src/routes/priceConfig/version/update.js | 32 +- src/routes/priceConfig/version/update.spec.js | 44 +- src/routes/productCategories/create.js | 48 +- src/routes/productCategories/create.spec.js | 147 +- src/routes/productCategories/delete.js | 11 +- src/routes/productCategories/delete.spec.js | 47 +- src/routes/productCategories/get.js | 56 +- src/routes/productCategories/get.spec.js | 30 +- src/routes/productCategories/list.js | 29 +- src/routes/productCategories/list.spec.js | 28 +- src/routes/productCategories/update.js | 45 +- src/routes/productCategories/update.spec.js | 202 +- src/routes/productTemplates/create.js | 66 +- src/routes/productTemplates/create.spec.js | 170 +- src/routes/productTemplates/delete.js | 12 +- src/routes/productTemplates/delete.spec.js | 83 +- src/routes/productTemplates/get.js | 60 +- src/routes/productTemplates/get.spec.js | 36 +- src/routes/productTemplates/list.js | 43 +- src/routes/productTemplates/list.spec.js | 39 +- src/routes/productTemplates/update.js | 66 +- src/routes/productTemplates/update.spec.js | 136 +- src/routes/productTemplates/upgrade.js | 36 +- src/routes/productTemplates/upgrade.spec.js | 83 +- src/routes/projectMemberInvites/create.js | 10 +- .../projectMemberInvites/create.spec.js | 52 +- src/routes/projectMemberInvites/get.spec.js | 8 +- .../projectMemberInvites/update.spec.js | 40 +- src/routes/projectMembers/create.spec.js | 140 +- src/routes/projectMembers/delete.spec.js | 18 +- src/routes/projectMembers/update.spec.js | 43 +- src/routes/projectTemplates/create.js | 83 +- src/routes/projectTemplates/create.spec.js | 182 +- src/routes/projectTemplates/delete.js | 20 +- src/routes/projectTemplates/delete.spec.js | 93 +- src/routes/projectTemplates/get.js | 60 +- src/routes/projectTemplates/get.spec.js | 36 +- src/routes/projectTemplates/list.js | 34 +- src/routes/projectTemplates/list.spec.js | 33 +- src/routes/projectTemplates/update.js | 82 +- src/routes/projectTemplates/update.spec.js | 174 +- src/routes/projectTemplates/upgrade.js | 56 +- src/routes/projectTemplates/upgrade.spec.js | 171 +- src/routes/projectTypes/create.js | 52 +- src/routes/projectTypes/create.spec.js | 164 +- src/routes/projectTypes/delete.js | 12 +- src/routes/projectTypes/delete.spec.js | 49 +- src/routes/projectTypes/get.js | 57 +- src/routes/projectTypes/get.spec.js | 30 +- src/routes/projectTypes/list.js | 30 +- src/routes/projectTypes/list.spec.js | 28 +- src/routes/projectTypes/update.js | 47 +- src/routes/projectTypes/update.spec.js | 242 +- src/routes/projectUpgrade/create.js | 31 +- src/routes/projectUpgrade/create.spec.js | 36 +- src/routes/projects/create.js | 102 +- src/routes/projects/create.spec.js | 119 +- src/routes/projects/delete.js | 10 +- src/routes/projects/delete.spec.js | 10 +- src/routes/projects/get.js | 4 +- src/routes/projects/get.spec.js | 22 +- src/routes/projects/list-db.js | 14 +- src/routes/projects/list-db.spec.js | 68 +- src/routes/projects/list.js | 85 +- src/routes/projects/list.spec.js | 111 +- src/routes/projects/update.js | 92 +- src/routes/projects/update.spec.js | 345 +- src/routes/timelines/create.spec.js | 90 +- src/routes/timelines/delete.spec.js | 32 +- src/routes/timelines/get.spec.js | 30 +- src/routes/timelines/list.spec.js | 38 +- src/routes/timelines/update.spec.js | 116 +- src/util.js | 52 +- 207 files changed, 8500 insertions(+), 8258 deletions(-) rename docs/{postman.json => Project API.postman_collection.json} (80%) rename docs/{postman_environment.json => Project API.postman_environment.json} (94%) diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index 4260d83e..c0edb2d3 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -10,7 +10,7 @@ "host": "PROJECTS_ES_URL", "apiVersion": "2.3", "indexName": "PROJECTS_ES_INDEX_NAME", - "docType": "projectV4", + "docType": "projectV5", "timelineIndexName": "TIMELINES_ES_INDEX_NAME", "timelineDocType": "TIMELINES_ES_DOC_TYPE" }, diff --git a/config/default.json b/config/default.json index 426be0be..7578a9bc 100644 --- a/config/default.json +++ b/config/default.json @@ -1,8 +1,8 @@ { - "apiVersion": "v4", + "apiVersion": "v5", "AUTH_SECRET": "secret", "logLevel": "info", - "version": "v4", + "version": "v5", "captureLogs": "false", "enableFileUpload": "true", "logentriesToken": "", @@ -19,11 +19,13 @@ "projectAttachmentPathSuffix": "attachments", "elasticsearchConfig": { "host": "", - "apiVersion": "2.3", + "apiVersion": "7.0", "indexName": "projects", - "docType": "projectV4", + "docType": "projectV5", "timelineIndexName": "timelines", - "timelineDocType": "timelineV4" + "timelineDocType": "timelineV5", + "metadataIndexName": "metadata", + "metadataDocType": "metadataV5" }, "connectProjectUrl":"", "dbConfig": { @@ -44,13 +46,13 @@ "busApiToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicHJvamVjdC1zZXJ2aWNlIiwiaWF0IjoxNTEyNzQ3MDgyLCJleHAiOjE1MjEzODcwODJ9.PHuNcFDaotGAL8RhQXQMdpL8yOKXxjB5DbBIodmt7RE", "HEALTH_CHECK_URL": "_health", "maxPhaseProductCount": 1, - "AUTH0_CLIENT_ID": "", - "AUTH0_CLIENT_SECRET": "", - "AUTH0_AUDIENCE": "", - "AUTH0_URL": "", - "TOKEN_CACHE_TIME": "", + "AUTH0_CLIENT_ID": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC", + "AUTH0_CLIENT_SECRET": "v3dcgVp4-_Lgk-8UhyekOA7CyyBvFBl1xipl97QqeGy3WyTk3poyvpsDC0sRAKoo", + "AUTH0_AUDIENCE": "https://m2m.topcoder-dev.com/", + "AUTH0_URL": "https://topcoder-dev.auth0.com/oauth/token", + "TOKEN_CACHE_TIME": "86000", "whitelistedOriginsForUserIdAuth": "[\"https:\/\/topcoder-newauth.auth0.com\/\",\"https:\/\/api.topcoder-dev.com\"]", - "AUTH0_PROXY_SERVER_URL" : "", + "AUTH0_PROXY_SERVER_URL" : "https://topcoder-dev.auth0.com/oauth/token", "EMAIL_INVITE_FROM_NAME":"Topcoder", "EMAIL_INVITE_FROM_EMAIL":"noreply@connect.topcoder.com", "inviteEmailSubject": "You are invited to Topcoder", diff --git a/config/m2m.local.js b/config/m2m.local.js index 8fb26130..27967c82 100644 --- a/config/m2m.local.js +++ b/config/m2m.local.js @@ -25,9 +25,9 @@ if (process.env.NODE_ENV === 'test') { elasticsearchConfig: { host: 'dockerhost:9200', // target elasticsearch 2.3 version - apiVersion: '2.3', + apiVersion: '7.0', indexName: 'projects', - docType: 'projectV4' + docType: 'projectV5' }, whitelistedOriginsForUserIdAuth: "[\"\"]", }; diff --git a/config/mock.local.js b/config/mock.local.js index bec4dd70..d9f9b786 100644 --- a/config/mock.local.js +++ b/config/mock.local.js @@ -25,9 +25,9 @@ if (process.env.NODE_ENV === 'test') { elasticsearchConfig: { host: 'dockerhost:9200', // target elasticsearch 2.3 version - apiVersion: '2.3', + apiVersion: '7.0', indexName: 'projects', - docType: 'projectV4' + docType: 'projectV5' }, whitelistedOriginsForUserIdAuth: "[\"\"]", }; diff --git a/config/test.json b/config/test.json index 73b0fe82..976241c7 100644 --- a/config/test.json +++ b/config/test.json @@ -6,11 +6,11 @@ "logentriesToken": "", "elasticsearchConfig": { "host": "http://localhost:9200", - "apiVersion": "2.3", + "apiVersion": "7.0", "indexName": "projects_test", - "docType": "projectV4", + "docType": "projectV5", "timelineIndexName": "timelines_test", - "timelineDocType": "timelineV4" + "timelineDocType": "timelineV5" }, "rabbitmqUrl": "amqp://localhost:5672", "connectProjectsUrl": "https://local.topcoder-dev.com/projects/", diff --git a/docs/postman.json b/docs/Project API.postman_collection.json similarity index 80% rename from docs/postman.json rename to docs/Project API.postman_collection.json index 3e17f076..714891f5 100644 --- a/docs/postman.json +++ b/docs/Project API.postman_collection.json @@ -1,7 +1,7 @@ { "info": { - "_postman_id": "57206894-511c-4ffb-94bb-e50d2dd416fb", - "name": "tc-project-service", + "_postman_id": "e76a2541-aafc-4944-92c5-e7bde5bf7930", + "name": "Project API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ @@ -27,15 +27,14 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}" + "raw": "{\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] } @@ -58,15 +57,14 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }],\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}" + "raw": "{\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }],\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] } @@ -89,15 +87,14 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"invalid\":3,\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }],\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}" + "raw": "{\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"invalid\":3,\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }],\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] } @@ -118,17 +115,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/2", + "raw": "{{api-url}}/projects/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "2" ] @@ -155,12 +147,11 @@ "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\",\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }]\n }\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/2", + "raw": "{{api-url}}/projects/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "2" ] @@ -187,12 +178,11 @@ "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":null\n }\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/2", + "raw": "{{api-url}}/projects/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "2" ] @@ -219,12 +209,11 @@ "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":3\n }\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/2", + "raw": "{{api-url}}/projects/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "2" ] @@ -242,17 +231,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] } @@ -281,12 +265,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"title\": \"first attachment submission\",\n\t\t\"filePath\": \"asdjshdasdas/asdsadj/asdasd.png\",\n\t\t\"s3Bucket\": \"topcoder-project-service\",\n\t\t\"contentType\": \"application/png\"\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/7/attachments", + "raw": "{{api-url}}/projects/7/attachments", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "7", "attachments" @@ -315,12 +298,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"title\": \"first attachment submission updated\",\n\t\t\"description\": \"updated project attachment\"\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/7/attachments/2", + "raw": "{{api-url}}/projects/7/attachments/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "7", "attachments", @@ -350,12 +332,11 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/7/attachments/2", + "raw": "{{api-url}}/projects/7/attachments/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "7", "attachments", @@ -387,15 +368,14 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project with templateId\",\n\t\t\"description\": \"Hello I am a test project with templateId\",\n\t\t\"type\": \"generic\",\n\t\t\"templateId\": 3000\n\t}\n}" + "raw": "{\n\t\t\"name\": \"test project with templateId\",\n\t\t\"description\": \"Hello I am a test project with templateId\",\n\t\t\"type\": \"generic\",\n\t\t\"templateId\": 3000\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] } @@ -418,15 +398,14 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"name\": \"test project with templateId\",\n \"description\": \"Hello I am a test project with templateId\",\n \"type\": \"generic\",\n \"templateId\": 3\n }\n}" + "raw": "{\n \"name\": \"test project with templateId\",\n \"description\": \"Hello I am a test project with templateId\",\n \"type\": \"generic\",\n \"templateId\": 3\n }" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] } @@ -457,18 +436,17 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/1/members", + "raw": "{{api-url}}/projects/1/members", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "members" ] }, - "description": "Request payload is mandatory while creating project. If no request payload is specified this should result in 422 status code." + "description": "Request payload is mandatory while creating project. If no request payload is specified this should result in 400 status code." }, "response": [] }, @@ -491,18 +469,17 @@ "raw": "{\n\"param\":{\n\t\"role\": \"copilot\"\n}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/members", + "raw": "{{api-url}}/projects/1/members", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "members" ] }, - "description": "Certain fields are mandatory while creating project. If invalid fields are specified this should result in 422 status code." + "description": "Certain fields are mandatory while creating project. If invalid fields are specified this should result in 400 status code." }, "response": [] }, @@ -525,12 +502,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"userId\": 40051331,\n\t\t\"isPrimary\": true\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/7/members", + "raw": "{{api-url}}/projects/7/members", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "7", "members" @@ -559,12 +535,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"userId\": 40051331,\n\t\t\"isPrimary\": true\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/members", + "raw": "{{api-url}}/projects/1/members", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "members" @@ -593,12 +568,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"role\": \"manager\",\n\t\t\"userId\": 40051330,\n\t\t\"isPrimary\": true\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/7/members", + "raw": "{{api-url}}/projects/7/members", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "7", "members" @@ -627,12 +601,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"role\": \"customer\",\n\t\t\"userId\": 40051332,\n\t\t\"isPrimary\": true\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/7/members", + "raw": "{{api-url}}/projects/7/members", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "7", "members" @@ -661,12 +634,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"isPrimary\": true\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/members/1", + "raw": "{{api-url}}/projects/1/members/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "members", @@ -696,12 +668,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"isPrimary\": false\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/members/1", + "raw": "{{api-url}}/projects/1/members/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "members", @@ -731,12 +702,11 @@ "raw": " {\n \"param\": {\n \"role\": \"wrong\"\n }\n } " }, "url": { - "raw": "{{api-url}}/v4/projects/3/members/5", + "raw": "{{api-url}}/projects/3/members/5", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "3", "members", @@ -765,12 +735,11 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/3/members/5", + "raw": "{{api-url}}/projects/3/members/5", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "3", "members", @@ -800,12 +769,11 @@ "raw": " {\n \"param\": {\n \"role\": \"manager\",\n \"isPrimary\": true\n }\n } " }, "url": { - "raw": "{{api-url}}/v4/projects/1/members/2", + "raw": "{{api-url}}/projects/1/members/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "members", @@ -839,16 +807,15 @@ "raw": "{\n\t\n}" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] }, - "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code." + "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 400 status code." }, "response": [] }, @@ -868,24 +835,38 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t}\n}" + "raw": "{\n}" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] }, - "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." + "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 400 status code." }, "response": [] }, { "name": "Create project with valid values", + "event": [ + { + "listen": "test", + "script": { + "id": "5d951b47-5aac-4af6-a24b-ef6c998b913e", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -900,15 +881,14 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n\t}\n}" + "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] }, @@ -932,15 +912,14 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n\t}\n}" + "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] }, @@ -958,19 +937,14 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/7", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "7" + "{{projectId}}" ] }, "description": "Get a project by id. project members and attachments should also be returned." @@ -987,19 +961,14 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1?fields=id,name,description,members.id,members.projectId", + "raw": "{{api-url}}/projects/{{projectId}}?fields=id,name,description,members.id,members.projectId", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "1" + "{{projectId}}" ], "query": [ { @@ -1022,17 +991,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/db", + "raw": "{{api-url}}/projects/db", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "db" ] @@ -1051,17 +1015,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/db?limit=1&offset=1", + "raw": "{{api-url}}/projects/db?limit=1&offset=1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "db" ], @@ -1081,7 +1040,7 @@ "response": [] }, { - "name": "List projects DB with filters applied", + "name": "List projects DB with type filter", "request": { "method": "GET", "header": [ @@ -1090,24 +1049,53 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" + "url": { + "raw": "{{api-url}}/projects/db?type=generic", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "db" + ], + "query": [ + { + "key": "type", + "value": "generic" + } + ] }, + "description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable" + }, + "response": [] + }, + { + "name": "List projects DB with id filter", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], "url": { - "raw": "{{api-url}}/v4/projects/db?filter=type%3Dgeneric", + "raw": "{{api-url}}/projects/db?id=1&id=2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "db" ], "query": [ { - "key": "filter", - "value": "type%3Dgeneric" + "key": "id", + "value": "1" + }, + { + "key": "id", + "value": "2" } ] }, @@ -1125,17 +1113,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/db?sort=type%20desc", + "raw": "{{api-url}}/projects/db?sort=type%20desc", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "db" ], @@ -1160,17 +1143,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/db?fields=id,name,description", + "raw": "{{api-url}}/projects/db?fields=id,name,description", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "db" ], @@ -1195,17 +1173,12 @@ "value": "Bearer {{jwt-token-copilot-40051332}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/db", + "raw": "{{api-url}}/projects/db", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "db" ] @@ -1223,17 +1196,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] }, @@ -1251,17 +1219,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects?limit=1&offset=1", + "raw": "{{api-url}}/projects?limit=1&offset=1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ], "query": [ @@ -1280,7 +1243,7 @@ "response": [] }, { - "name": "List projects with filters applied", + "name": "List projects with type filter", "request": { "method": "GET", "header": [ @@ -1289,23 +1252,51 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" + "url": { + "raw": "{{api-url}}/projects?type=generic", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects" + ], + "query": [ + { + "key": "type", + "value": "generic" + } + ] }, + "description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable" + }, + "response": [] + }, + { + "name": "List projects with id filter", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], "url": { - "raw": "{{api-url}}/v4/projects?filter=type%3Dgeneric", + "raw": "{{api-url}}/projects?id=1&id=2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ], "query": [ { - "key": "filter", - "value": "type%3Dgeneric" + "key": "id", + "value": "1" + }, + { + "key": "id", + "value": "2" } ] }, @@ -1323,23 +1314,18 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects?sort=type%20desc", + "raw": "{{api-url}}/projects?sort=type asc", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ], "query": [ { "key": "sort", - "value": "type%20desc" + "value": "type asc" } ] }, @@ -1357,17 +1343,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects?fields=id,name,description", + "raw": "{{api-url}}/projects?fields=id,name,description", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ], "query": [ @@ -1391,17 +1372,12 @@ "value": "Bearer {{jwt-token-copilot-40051332}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] } @@ -1423,14 +1399,13 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/3", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "3" + "{{projectId}}" ] }, "description": "Delete a project by id" @@ -1453,17 +1428,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"name\": \"project name updated\"\n }\n}" + "raw": "{\n \"name\": \"project name updated\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/1", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "1" + "{{projectId}}" ] }, "description": "Update the project name. Name should be updated successfully." @@ -1486,17 +1460,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}" + "raw": "{\n\t\t\"name\": \"project name updated\"\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects/2", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "2" + "{{projectId}}" ] }, "description": "Update the project name. If user don't have permission to the project than it should return 403." @@ -1519,17 +1492,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"project name updated\"\n\t}\n}" + "raw": "{\n\t\t\"name\": \"project name updated\"\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects/10", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "10" + "{{projectId}}" ] }, "description": "Update the project name. If project is not found than this result in 404 status code." @@ -1552,17 +1524,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"status\": \"in_review\"\n }\n}" + "raw": "{\n \"status\": \"in_review\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/1", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "1" + "{{projectId}}" ] }, "description": "Update the project status." @@ -1585,17 +1556,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"status\": \"reviewed\"\n }\n}" + "raw": "{\n \"status\": \"reviewed\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/7", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "7" + "{{projectId}}" ] }, "description": "Update the project status." @@ -1618,17 +1588,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"status\": \"paused\"\n }\n}" + "raw": "{\n \"status\": \"paused\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/7", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "7" + "{{projectId}}" ] }, "description": "Update the project status." @@ -1651,17 +1620,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"status\": \"cancelled\",\n \"cancelReason\": \"price/cost\"\n }\n}" + "raw": "{\n \"status\": \"cancelled\",\n \"cancelReason\": \"price/cost\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/7", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "7" + "{{projectId}}" ] }, "description": "Update the project status. While cancelling the project `cancelReason` is mandatory." @@ -1684,20 +1652,19 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"status\": \"cancelled\"\n\t}\n}" + "raw": "{\n\t\t\"status\": \"cancelled\"\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects/1", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "1" + "{{projectId}}" ] }, - "description": "Update the project status. While cancelling the project `cancelReason` is mandatory. If no `cancelReason` is supplied this should result in 422 status code." + "description": "Update the project status. While cancelling the project `cancelReason` is mandatory. If no `cancelReason` is supplied this should result in 400 status code." }, "response": [] }, @@ -1717,17 +1684,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"status\": \"completed\"\n }\n}" + "raw": "{\n \"status\": \"completed\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/7", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "7" + "{{projectId}}" ] }, "description": "Update the project status." @@ -1750,17 +1716,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"status\": \"active\"\n\t}\n}" + "raw": "{\n\t\t\"status\": \"active\"\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects/1", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "1" + "{{projectId}}" ] }, "description": "Move a project out of cancel state. Only admin and manager is allowed to do so." @@ -1783,17 +1748,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"status\": \"active\"\n\t}\n}" + "raw": "{\n\t\t\"status\": \"active\"\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects/1", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "1" + "{{projectId}}" ] }, "description": "Move a project out of cancel state. Only admin and manager is allowed to do so." @@ -1816,17 +1780,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"details\": {\n \"summary\": \"project name updated\"\n }\n }\n}" + "raw": "{\n \"details\": {\n \"summary\": \"project name updated\"\n }\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/8", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "8" + "{{projectId}}" ] }, "description": "Update the project details. This should fire specification modified event" @@ -1849,17 +1812,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"bookmarks\": [\n {\n \"title\": \"test\",\n \"address\": \"http://topcoder.com\"\n }\n \n ]\n }\n}" + "raw": "{\n \"bookmarks\": [\n {\n \"title\": \"test\",\n \"address\": \"http://topcoder.com\"\n }\n \n ]\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/8", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "8" + "{{projectId}}" ] }, "description": "Update the project bookmarks. This should fire project link created event" @@ -1882,17 +1844,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}" + "raw": "{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/1", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "1" + "{{projectId}}" ] } }, @@ -1914,17 +1875,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}" + "raw": "{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/1", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "1" + "{{projectId}}" ] } }, @@ -1946,17 +1906,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \n \"param\":{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }\n}" + "raw": "{\n \"name\": \"updatedProject name\",\n \"status\": \"active\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/1", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "1" + "{{projectId}}" ] } }, @@ -1982,10 +1941,6 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { "raw": "https://api.topcoder-dev.com/v3/direct/projects", "protocol": "https", @@ -2019,15 +1974,14 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }\n}" + "raw": "{\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects" ] } @@ -2053,12 +2007,11 @@ "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/members", + "raw": "{{api-url}}/projects/1/members", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "members" @@ -2086,12 +2039,11 @@ "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/2/members", + "raw": "{{api-url}}/projects/2/members", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "2", "members" @@ -2119,12 +2071,11 @@ "raw": " {\n \"param\": {\n \"role\": \"customer\",\n \"isPrimary\": true\n }\n } " }, "url": { - "raw": "{{api-url}}/v4/projects/2/members/4", + "raw": "{{api-url}}/projects/2/members/4", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "2", "members", @@ -2150,15 +2101,14 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }\n}" + "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/2", + "raw": "{{api-url}}/projects/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "2" ] @@ -2185,12 +2135,11 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/2/members/4", + "raw": "{{api-url}}/projects/2/members/4", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "2", "members", @@ -2246,12 +2195,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t}\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases", + "raw": "{{api-url}}/projects/1/phases", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases" @@ -2279,12 +2227,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t},\n\t\t\"order\": 1\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases", + "raw": "{{api-url}}/projects/1/phases", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases" @@ -2312,12 +2259,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t},\n\t\t\"order\": 1,\n\t\t\"productTemplateId\": 1\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases", + "raw": "{{api-url}}/projects/1/phases", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases" @@ -2340,17 +2286,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/db", + "raw": "{{api-url}}/projects/1/phases/db", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2374,17 +2315,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/db?fields=status,name,budget", + "raw": "{{api-url}}/projects/1/phases/db?fields=status,name,budget", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2414,17 +2350,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/db?sort=status desc", + "raw": "{{api-url}}/projects/1/phases/db?sort=status desc", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2454,17 +2385,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/db?sort=order desc", + "raw": "{{api-url}}/projects/1/phases/db?sort=order desc", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2494,17 +2420,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases", + "raw": "{{api-url}}/projects/1/phases", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases" @@ -2527,17 +2448,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases?fields=status,name,budget", + "raw": "{{api-url}}/projects/1/phases?fields=status,name,budget", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases" @@ -2566,17 +2482,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases?sort=status desc", + "raw": "{{api-url}}/projects/1/phases?sort=status desc", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases" @@ -2605,17 +2516,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases?sort=order desc", + "raw": "{{api-url}}/projects/1/phases?sort=order desc", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases" @@ -2644,17 +2550,12 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/1", + "raw": "{{api-url}}/projects/1/phases/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2683,12 +2584,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase xxx\",\n\t\t\"status\": \"inactive\",\n\t\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\t\"budget\": 30,\n\t\t\"progress\": 15,\n\t\t\"details\": {\n\t\t\t\"message\": \"phase details\"\n\t\t}\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/1", + "raw": "{{api-url}}/projects/1/phases/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2717,12 +2617,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase xxx\",\n\t\t\"status\": \"inactive\",\n\t\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\t\"budget\": 30,\n\t\t\"progress\": 15,\n\t\t\"details\": {\n\t\t\t\"message\": \"phase details\"\n\t\t},\n\t\t\"order\": 1\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/1", + "raw": "{{api-url}}/projects/1/phases/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2751,12 +2650,11 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/3", + "raw": "{{api-url}}/projects/1/phases/3", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2790,12 +2688,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test phase product\",\n\t\t\"type\": \"type 1\",\n\t\t\"estimatedPrice\": 10\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/1/products", + "raw": "{{api-url}}/projects/1/phases/1/products", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2816,17 +2713,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/1/products/db", + "raw": "{{api-url}}/projects/1/phases/1/products/db", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2848,17 +2740,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/1/products", + "raw": "{{api-url}}/projects/1/phases/1/products", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2879,17 +2766,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/1/products/1", + "raw": "{{api-url}}/projects/1/phases/1/products/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2920,12 +2802,11 @@ "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test phase product xxx\",\n\t\t\"type\": \"type 2\",\n\t\t\"templateId\": 10,\n\t\t\"estimatedPrice\": 1.234567,\n\t\t\"actualPrice\": 2.34567,\n\t\t\"details\": {\n\t\t\t\"message\": \"this is a JSON type. You can use any json\"\n\t\t}\n\t}\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/1/products/1", + "raw": "{{api-url}}/projects/1/phases/1/products/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2952,12 +2833,11 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/1/phases/1/products/1", + "raw": "{{api-url}}/projects/1/phases/1/products/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "1", "phases", @@ -2976,6 +2856,21 @@ "item": [ { "name": "Create project template", + "event": [ + { + "listen": "test", + "script": { + "id": "2f79c07b-8076-4715-abf7-1d6903df444f", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -2990,15 +2885,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"scope\":{\r\n \"scope1\":\"scope 1\"\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\"\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"scope\":{\r\n \"scope1\":\"scope 1\"\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\"\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates" @@ -3009,6 +2903,21 @@ }, { "name": "Create project template with form, priceConfig, planConfig", + "event": [ + { + "listen": "test", + "script": { + "id": "4c442ea3-0834-4a30-8044-a4e94fd4ea2d", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -3023,15 +2932,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates" @@ -3042,6 +2950,21 @@ }, { "name": "Create project template with only form key", + "event": [ + { + "listen": "test", + "script": { + "id": "7d0ae3ca-fe2d-40eb-b5c8-9b03955babec", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -3056,15 +2979,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\"\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\"\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates" @@ -3089,15 +3011,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"wrong-key\"\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"wrong-key\"\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates" @@ -3122,15 +3043,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates" @@ -3153,17 +3073,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates" @@ -3186,21 +3101,16 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates", - "1" + "{{projectTemplateId}}" ] } }, @@ -3222,19 +3132,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 2\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"qa\",\t\r\n \t \"version\": 2\t\r\n }\r\n }\r\n}" + "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 2\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"qa\",\t\r\n \t \"version\": 2\t\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/2/upgrade", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates", - "2", + "{{projectTemplateId}}", "upgrade" ] } @@ -3257,19 +3166,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n }\r\n }\r\n}" + "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1/upgrade", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates", - "1", + "{{projectTemplateId}}", "upgrade" ] } @@ -3295,16 +3203,15 @@ "raw": "{\r\n \"param\":{ \r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/3/upgrade", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates", - "3", + "{{projectTemplateId}}", "upgrade" ] } @@ -3327,19 +3234,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates", - "1" + "{{projectTemplateId}}" ] } }, @@ -3361,19 +3267,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates", - "1" + "{{projectTemplateId}}" ] } }, @@ -3395,19 +3300,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n\t\"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n\t\"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/1", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates", - "1" + "{{projectTemplateId}}" ] } }, @@ -3429,19 +3333,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTemplates/2", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTemplates", - "2" + "{{projectTemplateId}}" ] } }, @@ -3454,6 +3357,21 @@ "item": [ { "name": "Create product template", + "event": [ + { + "listen": "test", + "script": { + "id": "b5aaf185-6026-4b58-b9b8-56616109cd5a", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"productTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -3468,15 +3386,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\": {\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"template\": {\r\n \"template1\": {\r\n \"name\": \"template 1\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1\"\r\n },\r\n \"others\": [\"others 11\", \"others 12\"]\r\n },\r\n \"template2\": {\r\n \"name\": \"template 2\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 2\"\r\n },\r\n \"others\": [\"others 21\", \"others 22\"]\r\n }\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"subCategory\": \"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"template\": {\r\n \"template1\": {\r\n \"name\": \"template 1\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1\"\r\n },\r\n \"others\": [\"others 11\", \"others 12\"]\r\n },\r\n \"template2\": {\r\n \"name\": \"template 2\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 2\"\r\n },\r\n \"others\": [\"others 21\", \"others 22\"]\r\n }\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates" @@ -3487,6 +3404,21 @@ }, { "name": "Create product template with form", + "event": [ + { + "listen": "test", + "script": { + "id": "d5a2af2e-97d2-415c-a533-1d52dd4003c7", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"productTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -3501,15 +3433,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\": {\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1\r\n\t}\r\n }\r\n}" + "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1\r\n\t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates" @@ -3534,15 +3465,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\": {\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"wrong-key\"\r\n\t}\r\n }\r\n}" + "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"wrong-key\"\r\n\t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates" @@ -3567,15 +3497,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\": {\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1123\r\n\t}\r\n }\r\n}" + "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1123\r\n\t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates" @@ -3598,17 +3527,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates" @@ -3631,21 +3555,16 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates/3", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates", - "3" + "{{productTemplateId}}" ] } }, @@ -3667,19 +3586,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"productKey\":\"new productKey\",\r\n \"category\":\"key1\",\r\n \"icon\":\"http://example.com/icon-new.ico\",\r\n \"brief\": \"new brief\",\r\n \"details\": \"new details\",\r\n \"aliases\":{\r\n \"alias1\":\"scope 1\",\r\n \"alias2\": [\"a\"]\r\n },\r\n \"template\":{\r\n \"template1\":\"template 1\",\r\n \"template2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"productKey\":\"new productKey\",\r\n \"category\":\"key1\",\r\n \"icon\":\"http://example.com/icon-new.ico\",\r\n \"brief\": \"new brief\",\r\n \"details\": \"new details\",\r\n \"aliases\":{\r\n \"alias1\":\"scope 1\",\r\n \"alias2\": [\"a\"]\r\n },\r\n \"template\":{\r\n \"template1\":\"template 1\",\r\n \"template2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates/1", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates", - "1" + "{{productTemplateId}}" ] } }, @@ -3704,16 +3622,15 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates/1", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates", - "1" + "{{productTemplateId}}" ] } }, @@ -3737,19 +3654,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 2\r\n }\r\n }\r\n}" + "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 2\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates/2/upgrade", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates", - "2", + "{{productTemplateId}}", "upgrade" ] } @@ -3774,19 +3690,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 1234\r\n }\r\n }\r\n}" + "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 1234\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates/1/upgrade", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates", - "1", + "{{productTemplateId}}", "upgrade" ] } @@ -3811,19 +3726,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{ \r\n }\r\n}" + "raw": "{\r\n \r\n}" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productTemplates/3/upgrade", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productTemplates", - "3", + "{{productTemplateId}}", "upgrade" ] } @@ -3837,6 +3751,21 @@ "item": [ { "name": "Create project type", + "event": [ + { + "listen": "test", + "script": { + "id": "fbc45946-a3f2-433a-8ec5-0af82b69d2bd", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectTypeId\", pm.response.json().key);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -3851,15 +3780,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"key\": \"new key\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"http://example.com/icon4.ico\",\r\n \t\"question\": \"question 4\",\r\n \t\"info\": \"info 4\",\r\n \t\"aliases\": [\"key-41\", \"key_42\"],\r\n \t\"metadata\": {}\r\n }\r\n}" + "raw": "{\r\n \"key\": \"new key\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"http://example.com/icon4.ico\",\r\n \t\"question\": \"question 4\",\r\n \t\"info\": \"info 4\",\r\n \t\"aliases\": [\"key-41\", \"key_42\"],\r\n \t\"metadata\": {}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTypes", + "raw": "{{api-url}}/projects/metadata/projectTypes", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTypes" @@ -3882,17 +3810,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTypes", + "raw": "{{api-url}}/projects/metadata/projectTypes", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTypes" @@ -3915,21 +3838,16 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTypes/generic", + "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTypes", - "generic" + "{{projectTypeId}}" ] } }, @@ -3951,19 +3869,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"displayName\": \"Chatbot-updated\"\r\n }\r\n}" + "raw": "{\r\n \"displayName\": \"Chatbot-updated\"\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTypes/chatbot", + "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTypes", - "chatbot" + "{{projectTypeId}}" ] } }, @@ -3988,16 +3905,15 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/projectTypes/chatbot", + "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "projectTypes", - "chatbot" + "{{projectTypeId}}" ] } }, @@ -4010,6 +3926,21 @@ "item": [ { "name": "Create product category", + "event": [ + { + "listen": "test", + "script": { + "id": "06156797-ceb2-4f8c-9448-5c453adb7b7a", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"productCategoryId\", pm.response.json().key);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -4024,15 +3955,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"key\": \"generic\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"icon\",\r\n \"question\": \"question\",\r\n \"info\": \"info\",\r\n \"aliases\": [\"key-1\", \"key-2\"]\r\n }\r\n}" + "raw": "{\r\n \"key\": \"generic\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"icon\",\r\n \"question\": \"question\",\r\n \"info\": \"info\",\r\n \"aliases\": [\"key-1\", \"key-2\"]\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productCategories", + "raw": "{{api-url}}/projects/metadata/productCategories", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productCategories" @@ -4055,17 +3985,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productCategories", + "raw": "{{api-url}}/projects/metadata/productCategories", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productCategories" @@ -4088,21 +4013,16 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productCategories/generic", + "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productCategories", - "generic" + "{{productCategoryId}}" ] } }, @@ -4124,19 +4044,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"displayName\": \"Chatbot-updated\"\r\n }\r\n}" + "raw": "{\r\n \"displayName\": \"Chatbot-updated\"\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productCategories/generic", + "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productCategories", - "generic" + "{{productCategoryId}}" ] } }, @@ -4158,19 +4077,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/productCategories/generic", + "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "productCategories", - "generic" + "{{productCategoryId}}" ] } }, @@ -4229,17 +4147,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}\n}" + "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects/6/upgrade", + "raw": "{{api-url}}/projects/{{projectId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "6", + "{{projectId}}", "upgrade" ] } @@ -4262,17 +4179,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}\n}" + "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects/7/upgrade", + "raw": "{{api-url}}/projects/{{projectId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "7", + "{{projectId}}", "upgrade" ] } @@ -4295,17 +4211,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}\n}" + "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects/6/upgrade", + "raw": "{{api-url}}/projects/{{projectId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "6", + "{{projectId}}", "upgrade" ] } @@ -4328,17 +4243,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}\n}" + "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}" }, "url": { - "raw": "{{api-url}}/v4/projects/7/upgrade", + "raw": "{{api-url}}/projects/{{projectId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", - "7", + "{{projectId}}", "upgrade" ] } @@ -4370,12 +4284,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines", + "raw": "{{api-url}}/timelines", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines" ] } @@ -4401,12 +4314,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1,\r\n \"templateId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines", + "raw": "{{api-url}}/timelines", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines" ] } @@ -4432,12 +4344,11 @@ "raw": "{\r\n \"param\":{\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-28T00:00:00.000Z\",\r\n \"reference\": \"invalid\",\r\n \"referenceId\": 0\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines", + "raw": "{{api-url}}/timelines", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines" ] } @@ -4458,17 +4369,12 @@ "value": "Bearer {{jwt-token-copilot-40051332}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/timelines?filter=reference%3Dphase%26referenceId%3D1", + "raw": "{{api-url}}/timelines?filter=reference%3Dphase%26referenceId%3D1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines" ], "query": [ @@ -4495,17 +4401,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/timelines/1", + "raw": "{{api-url}}/timelines/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1" ] @@ -4532,12 +4433,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-01T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1", + "raw": "{{api-url}}/timelines/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1" ] @@ -4564,12 +4464,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1", + "raw": "{{api-url}}/timelines/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1" ] @@ -4596,12 +4495,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1", + "raw": "{{api-url}}/timelines/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1" ] @@ -4628,12 +4526,11 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/timelines/4", + "raw": "{{api-url}}/timelines/4", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "4" ] @@ -4665,12 +4562,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 4,\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-08T00:00:00.000Z\",\r\n \"status\": \"open\",\r\n \"type\": \"type3\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 2,\r\n 3,\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 3\",\r\n \"activeText\": \"activeText 3\",\r\n \"completedText\": \"completedText 3\",\r\n \"blockedText\": \"blockedText 3\"\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones", + "raw": "{{api-url}}/timelines/1/milestones", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones" @@ -4698,12 +4594,11 @@ "raw": "{\r\n \"param\":{\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-04T00:00:00.000Z\"\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones", + "raw": "{{api-url}}/timelines/1/milestones", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones" @@ -4726,17 +4621,12 @@ "value": "Bearer {{jwt-token-copilot-40051332}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones", + "raw": "{{api-url}}/timelines/1/milestones", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones" @@ -4759,17 +4649,12 @@ "value": "Bearer {{jwt-token-copilot-40051332}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones?sort=order desc", + "raw": "{{api-url}}/timelines/1/milestones?sort=order desc", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones" @@ -4798,17 +4683,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/1/milestones/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones", @@ -4837,12 +4717,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-09-28T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/1/milestones/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones", @@ -4871,12 +4750,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"active\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones/2", + "raw": "{{api-url}}/timelines/1/milestones/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones", @@ -4905,12 +4783,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"completed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones/2", + "raw": "{{api-url}}/timelines/1/milestones/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones", @@ -4939,12 +4816,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/1/milestones/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones", @@ -4973,12 +4849,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/1/milestones/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones", @@ -5007,12 +4882,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/1/milestones/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones", @@ -5041,12 +4915,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/1/milestones/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones", @@ -5075,12 +4948,11 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/timelines/1/milestones/2", + "raw": "{{api-url}}/timelines/1/milestones/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "1", "milestones", @@ -5114,12 +4986,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {}\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates" @@ -5147,12 +5018,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1000,\r\n\t\"metadata\": {}\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates" @@ -5180,12 +5050,11 @@ "raw": "{\r\n \"param\":{\r\n\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates" @@ -5213,12 +5082,11 @@ "raw": "{\r\n \"param\":{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/clone", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5247,12 +5115,11 @@ "raw": "{\r\n \"param\":{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2000\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/clone", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5281,12 +5148,11 @@ "raw": "{\r\n \"param\":{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1000,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/clone", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5310,17 +5176,12 @@ "value": "Bearer {{jwt-token-copilot-40051332}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates" @@ -5343,17 +5204,12 @@ "value": "Bearer {{jwt-token-copilot-40051332}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates" @@ -5382,17 +5238,12 @@ "value": "Bearer {{jwt-token-copilot-40051332}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1&sort=order desc", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1&sort=order desc", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates" @@ -5425,17 +5276,12 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5464,12 +5310,11 @@ "raw": "{\r\n \"param\":{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5498,12 +5343,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 2,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5532,12 +5376,11 @@ "raw": "{\r\n \"param\":{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5566,12 +5409,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 3,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5600,12 +5442,11 @@ "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5634,12 +5475,11 @@ "raw": "{\r\n \"param\":{\r\n\t\"name\": \"milestoneTemplate 5-updated\",\r\n\t\"description\": \"description 5-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type5-updated\",\r\n\t\"order\": 5,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {\r\n \"metadata1\": {\r\n \"name\": \"metadata 1 - update\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1 - update\",\r\n \"newDetails\": \"new\"\r\n },\r\n \"others\": [\"others new\"]\r\n },\r\n \"metadata3\": {\r\n \"name\": \"metadata 3\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 3\"\r\n },\r\n \"others\": [\"others 31\", \"others 32\"]\r\n }\r\n }\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5668,12 +5508,11 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/timelines/metadata/milestoneTemplates/2", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/2", "host": [ "{{api-url}}" ], "path": [ - "v4", "timelines", "metadata", "milestoneTemplates", @@ -5709,17 +5548,12 @@ "type": "text" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata", + "raw": "{{api-url}}/projects/metadata", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata" ] @@ -5742,17 +5576,12 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata?includeAllReferred=true", + "raw": "{{api-url}}/projects/metadata?includeAllReferred=true", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata" ], @@ -5786,17 +5615,12 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions", + "raw": "{{api-url}}/projects/metadata/form/dev/versions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", @@ -5822,23 +5646,18 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1", + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", - "dev", + "{{formKey}}", "versions", - "1" + "{{formVersion}}" ] } }, @@ -5859,21 +5678,16 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev", + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", - "dev" + "{{formKey}}" ] } }, @@ -5881,6 +5695,22 @@ }, { "name": "Create form", + "event": [ + { + "listen": "test", + "script": { + "id": "94f6be66-34cc-40c8-80c2-b27dd93ed527", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"formKey\", pm.response.json().key);", + " pm.environment.set(\"formVersion\", pm.response.json().version);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "auth": { "type": "bearer", @@ -5903,15 +5733,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions", + "raw": "{{api-url}}/projects/metadata/form/dev/versions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", @@ -5946,21 +5775,20 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1", + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", - "dev", + "{{formKey}}", "versions", - "1" + "{{formVersion}}" ] } }, @@ -5993,18 +5821,17 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1", + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", - "dev", + "{{formKey}}", "versions", - "1" + "{{formVersion}}" ] } }, @@ -6030,17 +5857,12 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/form/dev/versions/1/revisions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", @@ -6068,25 +5890,20 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1/revisions/1", + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions/{{formRevision}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", - "dev", + "{{formKey}}", "versions", - "1", + "{{formVersion}}", "revisions", - "1" + "{{formRevision}}" ] } }, @@ -6094,6 +5911,21 @@ }, { "name": "Create form", + "event": [ + { + "listen": "test", + "script": { + "id": "dbe5ec9f-022c-4ec5-b58c-d19c15430b61", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"formRevision\", pm.response.json().revision);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "auth": { "type": "bearer", @@ -6116,21 +5948,20 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", - "dev", + "{{formKey}}", "versions", - "1", + "{{formVersion}}", "revisions" ] } @@ -6161,15 +5992,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/no-exist-2222key36/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/form/no-exist-2222key36/versions/1/revisions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", @@ -6209,20 +6039,19 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/form/dev/versions/1/revisions/1", + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions/{{formRevision}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "form", - "dev", + "{{formKey}}", "versions", - "1", + "{{formVersion}}", "revisions", - "1" + "{{formRevision}}" ] } }, @@ -6248,17 +6077,12 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions", + "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", @@ -6284,23 +6108,18 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1", + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", - "dev", + "{{priceKey}}", "versions", - "1" + "{{priceVersion}}" ] } }, @@ -6321,21 +6140,16 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev", + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", - "dev" + "{{priceKey}}" ] } }, @@ -6343,6 +6157,22 @@ }, { "name": "Create priceConfig", + "event": [ + { + "listen": "test", + "script": { + "id": "e440c87c-49ff-4443-b9bf-b44d4e9a480f", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"priceKey\", pm.response.json().key);", + " pm.environment.set(\"priceVersion\", pm.response.json().version);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "auth": { "type": "bearer", @@ -6365,15 +6195,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions", + "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", @@ -6408,21 +6237,20 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1", + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", - "dev", + "{{priceKey}}", "versions", - "1" + "{{priceVersion}}" ] } }, @@ -6455,18 +6283,17 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1", + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", - "dev", + "{{priceKey}}", "versions", - "1" + "{{priceVersion}}" ] } }, @@ -6514,17 +6341,12 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/3/revisions", + "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions/3/revisions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", @@ -6552,25 +6374,20 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1", + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions/{{priceRevision}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", - "dev", + "{{priceKey}}", "versions", - "1", + "{{priceVersion}}", "revisions", - "1" + "{{priceRevision}}" ] } }, @@ -6578,6 +6395,21 @@ }, { "name": "Create price config", + "event": [ + { + "listen": "test", + "script": { + "id": "d53ed608-b21c-4d6f-bb68-c2beda1d631d", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"priceRevision\", pm.response.json().revision);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "auth": { "type": "bearer", @@ -6600,21 +6432,20 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", - "dev", + "{{priceKey}}", "versions", - "1", + "{{priceVersion}}", "revisions" ] } @@ -6645,15 +6476,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/no-exist-key/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/priceConfig/no-exist-key/versions/1/revisions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", @@ -6693,20 +6523,19 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1", + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions/{{priceRevision}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "priceConfig", - "dev", + "{{priceKey}}", "versions", - "1", + "{{priceVersion}}", "revisions", - "1" + "{{priceRevision}}" ] } }, @@ -6732,17 +6561,12 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions", + "raw": "{{api-url}}/projects/metadata/planConfig/dev/versions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", @@ -6768,23 +6592,18 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/3", + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", - "dev", + "{{planKey}}", "versions", - "3" + "{{planVersion}}" ] } }, @@ -6805,21 +6624,16 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev", + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", - "dev" + "{{planKey}}" ] } }, @@ -6827,6 +6641,22 @@ }, { "name": "Create plan config", + "event": [ + { + "listen": "test", + "script": { + "id": "97bc350a-0c4f-46a6-a315-a62b203b3ad2", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"planKey\", pm.response.json().key);", + " pm.environment.set(\"planVersion\", pm.response.json().version);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "auth": { "type": "bearer", @@ -6849,15 +6679,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions", + "raw": "{{api-url}}/projects/metadata/planConfig/dev/versions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", @@ -6892,21 +6721,20 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1", + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", - "dev", + "{{planKey}}", "versions", - "1" + "{{planVersion}}" ] } }, @@ -6939,18 +6767,17 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1", + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", - "dev", + "{{planKey}}", "versions", - "1" + "{{planVersion}}" ] } }, @@ -6976,23 +6803,18 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", - "dev", + "{{planKey}}", "versions", - "1", + "{{planVersion}}", "revisions" ] } @@ -7014,25 +6836,20 @@ }, "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1/revisions/1", + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions/{{planRevision}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", - "dev", + "{{planKey}}", "versions", - "1", + "{{planVersion}}", "revisions", - "1" + "{{planRevision}}" ] } }, @@ -7040,6 +6857,21 @@ }, { "name": "Create plan config", + "event": [ + { + "listen": "test", + "script": { + "id": "a5373f1f-4beb-46f9-8538-10c938c204ba", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"planRevision\", pm.response.json().revision);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "auth": { "type": "bearer", @@ -7062,21 +6894,20 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", - "dev", + "{{planKey}}", "versions", - "1", + "{{planVersion}}", "revisions" ] } @@ -7107,15 +6938,14 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }\r\n}" + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/no-exist-key/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/planConfig/no-exist-key/versions/1/revisions", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", @@ -7155,20 +6985,19 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/v4/projects/metadata/planConfig/dev/versions/1/revisions/1", + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions/{{planRevision}}", "host": [ "{{api-url}}" ], "path": [ - "v4", "projects", "metadata", "planConfig", - "dev", + "{{planKey}}", "versions", - "1", + "{{planVersion}}", "revisions", - "1" + "{{planRevision}}" ] } }, diff --git a/docs/postman_environment.json b/docs/Project API.postman_environment.json similarity index 94% rename from docs/postman_environment.json rename to docs/Project API.postman_environment.json index 3192362a..e153507d 100644 --- a/docs/postman_environment.json +++ b/docs/Project API.postman_environment.json @@ -1,10 +1,10 @@ { - "id": "be71a5b6-f6f0-413c-99ae-56f21f10dd53", - "name": "tc-project-service", + "id": "9408797f-cb90-43c1-b08b-375e30edb5bb", + "name": "Project API", "values": [ { "key": "api-url", - "value": "http://localhost:8001", + "value": "http://localhost:8001/v5", "description": "", "enabled": true }, @@ -58,6 +58,6 @@ } ], "_postman_variable_scope": "environment", - "_postman_exported_at": "2018-12-04T21:50:56.610Z", - "_postman_exported_using": "Postman/6.5.2" + "_postman_exported_at": "2019-06-07T11:02:18.794Z", + "_postman_exported_using": "Postman/6.5.3" } \ No newline at end of file diff --git a/local/seed/index.js b/local/seed/index.js index a98fcc1c..acc8a068 100644 --- a/local/seed/index.js +++ b/local/seed/index.js @@ -1,7 +1,7 @@ const seedMetadata = require('./seedMetadata'); const seedProjects = require('./seedProjects'); -const targetUrl = 'http://localhost:8001/v4/'; +const targetUrl = 'http://localhost:8001/v5/'; const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw'; diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index b4734c3c..b3d963fd 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -17,6 +17,8 @@ import util from '../src/util'; const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName'); +const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); +const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); // create new elasticsearch client // the client modifies the config object, so always passed the cloned object @@ -340,6 +342,289 @@ function getRequestBody(indexName) { }, }, }; + + const metadataMapping = { + _all: { enabled: false }, + properties: { + projectTemplates: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + category: { + type: 'string', + index: 'not_analyzed', + }, + name: { + type: 'string', + }, + id: { + type: 'long', + }, + scope: { + type: 'object', + }, + form: { + type: 'object', + }, + priceConfig: { + type: 'object', + }, + planConfig: { + type: 'object', + }, + phases: { + type: 'object', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + forms: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + version: { + type: 'integer', + }, + revision: { + type: 'integer', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + planConfigs: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + version: { + type: 'integer', + }, + revision: { + type: 'integer', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + priceConfigs: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + version: { + type: 'integer', + }, + revision: { + type: 'integer', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + orgConfigs: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + orgId: { + type: 'string', + index: 'not_analyzed', + }, + configName: { + type: 'string', + index: 'not_analyzed', + }, + configValue: { + type: 'string', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + productTemplates: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + name: { + type: 'string', + }, + productKey: { + type: 'string', + index: 'not_analyzed', + }, + category: { + type: 'string', + }, + subCategory: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + projectTypes: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + displayName: { + type: 'string', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + productCategories: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + displayName: { + type: 'string', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + }, + }; switch (indexName) { case ES_PROJECT_INDEX: result = { @@ -351,6 +636,16 @@ function getRequestBody(indexName) { }; result.body.mappings[ES_PROJECT_TYPE] = projectMapping; break; + case ES_METADATA_INDEX: + result = { + index: indexName, + updateAllTypes: true, + body: { + mappings: { }, + }, + }; + result.body.mappings[ES_METADATA_TYPE] = metadataMapping; + break; default: throw new Error(`Invalid index name '${indexName}'`); } @@ -367,6 +662,9 @@ esClient.indices.delete({ // Re-create timeline index .then(() => esClient.indices.delete({ index: ES_TIMELINE_INDEX, ignore: [404] })) .then(() => esClient.indices.create({ index: ES_TIMELINE_INDEX })) +// Re-create metadata index +.then(() => esClient.indices.delete({ index: ES_METADATA_INDEX, ignore: [404] })) +.then(() => esClient.indices.create(getRequestBody(ES_METADATA_INDEX))) .then(() => { console.log('elasticsearch indices synced successfully'); process.exit(); diff --git a/package-lock.json b/package-lock.json index 33011b24..7fbf46b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,15 +18,15 @@ "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.0.tgz", "integrity": "sha1-JjNHCk6r6aR82aRf2yDtX5NAe8o=" }, - "@types/geojson": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", - "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" - }, "@types/lodash": { - "version": "4.14.133", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.133.tgz", - "integrity": "sha512-/3JqnvPnY58GLzG3Y7fpphOhATV1DDZ/Ak3DQufjlRK5E4u+s0CfClfNFtAGBabw+jDGtRFbOZe+Z02ZMWCBNQ==" + "version": "4.14.134", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.134.tgz", + "integrity": "sha512-2/O0khFUCFeDlbi7sZ7ZFRCcT812fAeOLm7Ev4KbwASkZ575TDrDcY7YyaoHdTOzKcNbfiwLYZqPmoC4wadrsw==" + }, + "@types/node": { + "version": "12.0.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.7.tgz", + "integrity": "sha512-1YKeT4JitGgE4SOzyB9eMwO0nGVNkNEsm9qlIt1Lqm/tG2QEiSMTD4kS3aO6L+w5SClLVxALmIBESK6Mk5wX0A==" }, "abbrev": { "version": "1.0.9", @@ -35,18 +35,18 @@ "dev": true }, "accepts": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "mime-types": "~2.1.16", - "negotiator": "0.6.1" + "mime-types": "~2.1.24", + "negotiator": "0.6.2" } }, "acorn": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", - "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", "dev": true }, "acorn-jsx": { @@ -66,14 +66,23 @@ } } }, + "agentkeepalive": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "requires": { + "humanize-ms": "^1.2.1" + } + }, "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ajv-keywords": { @@ -82,32 +91,17 @@ "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", "dev": true }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, "amqplib": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.1.tgz", - "integrity": "sha1-fMz+ur5WwumE6noiQ/fO/m+/xs8=", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.3.tgz", + "integrity": "sha512-ZOdUhMxcF+u62rPI+hMtU1NBXSDFQ3eCJJrenamtdQ7YYwh7RZJHOIM1gonVbZ5PyVdYH4xqBPje9OYqk7fnqw==", "requires": { - "bitsyntax": "~0.0.4", - "bluebird": "^3.4.6", - "buffer-more-ints": "0.0.2", - "readable-stream": "1.x >=1.1.9" + "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" } }, "analytics-node": { @@ -168,6 +162,12 @@ } } }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, "ansi-escapes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", @@ -184,6 +184,11 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", @@ -209,17 +214,10 @@ "default-require-extensions": "^1.0.0" } }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true, + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { "sprintf-js": "~1.0.2" } @@ -229,6 +227,7 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -245,60 +244,27 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "array-slice": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.0.0.tgz", - "integrity": "sha1-5zA08A3MH0CHYAj9IP6ud71LfC8=", - "dev": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "array-uniq": "^1.0.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, "array-unique": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "dev": true, + "optional": true }, "asn1": { "version": "0.2.4", @@ -314,9 +280,9 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", - "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "assign-symbols": { @@ -332,15 +298,15 @@ "dev": true }, "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, "async-listener": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.8.tgz", - "integrity": "sha512-1Sy1jDhjlgxcSd9/ICHqiAHT8VSJ9R1lzEyWwP/4Hm9p8nVTNtU0SxG/Z15XHD/aZvQraSw9BpDU3EBcFnOVrw==", + "version": "0.6.10", + "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" @@ -369,81 +335,12 @@ "superagent": "^3.8.2", "url-join": "^4.0.0", "winchan": "^0.2.1" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "requires": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - } - } } }, "aws-sdk": { - "version": "2.468.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.468.0.tgz", - "integrity": "sha512-Bo4j1DLDBWSLgNsfpLNU2RKU2+24JzdFqgyyOKOyJ1p6RgrnDxcwoR2CPWK5olPU2cgXmIicetfOGDsC3LjLtg==", + "version": "2.471.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.471.0.tgz", + "integrity": "sha512-c7e1939Nep03xLyN+qV1uzzotxYVqtcj+5x87C1s3qOPSG7aSVIbtJfcu4RUdtsKty5qUef6muJ6ohNMRYrW6g==", "requires": { "buffer": "4.9.1", "events": "1.1.1", @@ -475,6 +372,22 @@ "is-buffer": "^2.0.2" }, "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, "is-buffer": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", @@ -516,9 +429,9 @@ } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -579,15 +492,6 @@ "regenerator-runtime": "^0.11.0" } }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, "json5": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", @@ -1466,9 +1370,9 @@ }, "dependencies": { "regenerator-runtime": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", - "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true } } @@ -1713,9 +1617,9 @@ } }, "base64-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", - "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -1725,12 +1629,6 @@ "tweetnacl": "^0.14.3" } }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, "bin-protocol": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/bin-protocol/-/bin-protocol-3.1.1.tgz", @@ -1742,44 +1640,49 @@ } }, "binary-extensions": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", - "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } }, "bitsyntax": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz", - "integrity": "sha1-6xDMb4K4xJDj6FaY8H6D1G4MuoI=", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", + "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", "requires": { - "buffer-more-ints": "0.0.2" + "buffer-more-ints": "~1.0.0", + "debug": "~2.6.9", + "safe-buffer": "~5.1.2" } }, "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" }, "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "requires": { - "bytes": "3.0.0", + "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" } }, "boxen": { @@ -1872,9 +1775,9 @@ } }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1885,12 +1788,19 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, + "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", "repeat-element": "^1.1.2" } }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -1918,21 +1828,21 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, "buffer-more-ints": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", - "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" }, "buffer-writer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", - "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" }, "bunyan": { "version": "1.8.12", @@ -1946,9 +1856,9 @@ } }, "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "cache-base": { "version": "1.0.1", @@ -1991,11 +1901,10 @@ "dev": true }, "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true }, "capture-stack-trace": { "version": "1.0.1", @@ -2008,17 +1917,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, "chai": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", @@ -2129,17 +2027,17 @@ "dev": true }, "cli-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.2.0.tgz", - "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", "dev": true, "requires": { "ansi-regex": "^2.1.1", "d": "1", - "es5-ext": "^0.10.12", - "es6-iterator": "2", - "memoizee": "^0.4.3", - "timers-ext": "0.1" + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.5" } }, "cli-cursor": { @@ -2158,36 +2056,62 @@ "dev": true }, "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, - "optional": true, "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" }, "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, - "optional": true + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } } } }, "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true + "cls-bluebird": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "requires": { + "is-bluebird": "^1.0.2", + "shimmer": "^1.1.0" + } }, "co": { "version": "4.6.0", @@ -2241,27 +2165,27 @@ "dev": true }, "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" }, "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "component-type": { "version": "1.2.1", @@ -2269,60 +2193,31 @@ "integrity": "sha1-ikeQFwAjjk/DIml3EjAibyS0Fak=" }, "compressible": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", - "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", "requires": { - "mime-db": ">= 1.36.0 < 2" - }, - "dependencies": { - "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" - } + "mime-db": ">= 1.40.0 < 2" } }, "compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", "requires": { "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "~2.0.14", + "compressible": "~2.0.16", "debug": "2.6.9", - "on-headers": "~1.0.1", + "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" }, "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" - }, - "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", - "requires": { - "mime-db": "~1.37.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" } } }, @@ -2332,11 +2227,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "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" @@ -2349,24 +2245,24 @@ "dev": true }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", + "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -2375,18 +2271,17 @@ } }, "config": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/config/-/config-1.27.0.tgz", - "integrity": "sha1-OrMNAID/dvQHwvR6wTJq39kIr18=", + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/config/-/config-1.31.0.tgz", + "integrity": "sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA==", "requires": { - "json5": "0.4.0", - "os-homedir": "1.0.2" + "json5": "^1.0.1" } }, "config-chain": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", - "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "dev": true, "requires": { "ini": "^1.3.4", @@ -2419,9 +2314,12 @@ "dev": true }, "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } }, "content-type": { "version": "1.0.4", @@ -2429,34 +2327,37 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "continuation-local-storage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.0.tgz", - "integrity": "sha1-4Z/Da1lwkKXU5KOy6j68XilpSiQ=", + "version": "3.2.1", + "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.0.1" + "emitter-listener": "^1.1.1" } }, "convert-source-map": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", - "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", - "dev": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookiejar": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", - "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" }, "copy-descriptor": { "version": "0.1.1", @@ -2465,9 +2366,9 @@ "dev": true }, "core-js": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", - "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=" + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" }, "core-util-is": { "version": "1.0.2", @@ -2475,9 +2376,9 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cors": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", - "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "version": "2.8.5", + "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" @@ -2493,26 +2394,16 @@ } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - } } }, "crypto-js": { @@ -2548,12 +2439,6 @@ "assert-plus": "^1.0.0" } }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2623,21 +2508,13 @@ } } }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "clone": "^1.0.2" - }, - "dependencies": { - "clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", - "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", - "dev": true - } + "object-keys": "^1.0.12" } }, "define-property": { @@ -2693,51 +2570,21 @@ } } }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "deprecated": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", - "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, - "detect-file": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz", - "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", - "dev": true, - "requires": { - "fs-exists-sync": "^0.1.0" - } - }, "detect-indent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", @@ -2748,27 +2595,18 @@ } }, "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "doctrine": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", - "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "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", - "isarray": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } + "esutils": "^2.0.2" } }, "dot-prop": { @@ -2781,26 +2619,17 @@ } }, "dottie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-1.1.1.tgz", - "integrity": "sha1-RcKj9IvWUo7u0memmoSOqspvqmo=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", + "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw==" }, "dtrace-provider": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.5.tgz", - "integrity": "sha1-mOu6Ihr6xG4cOf02hY2Pk2dSS5I=", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz", + "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", "optional": true, "requires": { - "nan": "^2.3.3" - } - }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9" + "nan": "^2.10.0" } }, "duplexer3": { @@ -2819,33 +2648,33 @@ } }, "ecdsa-sig-formatter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", - "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "version": "1.0.11", + "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" } }, "editorconfig": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz", - "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", "dev": true, "requires": { - "bluebird": "^3.0.5", - "commander": "^2.9.0", - "lru-cache": "^3.2.0", - "semver": "^5.1.0", + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", "sigmund": "^1.0.1" }, "dependencies": { "lru-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", - "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { - "pseudomap": "^1.0.1" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } } } @@ -2856,81 +2685,86 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "elasticsearch": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-11.0.1.tgz", - "integrity": "sha1-0YBoTGvefs+g+iTmL6HIcu6uCOc=", + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-16.1.1.tgz", + "integrity": "sha512-OF2fIjcTPfq/4Tj6k4/SZr2IIlfWlBBQoy/em225mfevYFW1abN3nyXKWldXGV+eWI6LWNqB8lb3hAP4d6Rh/Q==", "requires": { + "agentkeepalive": "^3.4.1", "chalk": "^1.0.0", - "forever-agent": "^0.6.0", - "lodash": "^3.10.0", - "lodash-compat": "^3.0.0", - "promise": "^7.1.1" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - } + "lodash": "^4.17.10" } }, "emitter-listener": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.0.1.tgz", - "integrity": "sha1-skmepuWCMKUsJo1d8mHuzZ8Q/pc=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", "requires": { - "shimmer": "1.0.0" - }, - "dependencies": { - "shimmer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.0.0.tgz", - "integrity": "sha1-ScLXHGeDYLgCvhiyeDgtHLuAXDk=" - } + "shimmer": "^1.2.0" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "encodeurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "end-of-stream": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", - "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "~1.3.0" - }, - "dependencies": { - "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "dev": true, - "requires": { - "wrappy": "1" - } - } + "once": "^1.4.0" } }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es5-ext": { - "version": "0.10.35", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.35.tgz", - "integrity": "sha1-GO6FjOajxFx9eekcFfzKnsVoSU8=", + "version": "0.10.50", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", + "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", "dev": true, "requires": { - "es6-iterator": "~2.0.1", - "es6-symbol": "~3.1.1" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" } }, "es6-iterator": { @@ -2958,6 +2792,11 @@ "event-emitter": "~0.3.5" } }, + "es6-promise-polyfill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es6-promise-polyfill/-/es6-promise-polyfill-1.2.0.tgz", + "integrity": "sha1-84kl8jyz4+jObNqP93T867sJDN4=" + }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", @@ -2982,14 +2821,14 @@ } }, "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", "dev": true, "requires": { "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.1" } }, @@ -3059,9 +2898,9 @@ }, "dependencies": { "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3093,41 +2932,42 @@ } }, "eslint-import-resolver-node": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz", - "integrity": "sha512-yUtXS15gIcij68NmXmP9Ni77AQuCN0itXbCc/jWd8C6/yKZaSNXicpC8cgvjnxVdmfsosIXrjpzFq7GcDryb6A==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", "dev": true, "requires": { - "debug": "^2.6.8", - "resolve": "^1.2.0" + "debug": "^2.6.9", + "resolve": "^1.5.0" } }, "eslint-module-utils": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", - "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz", + "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", "dev": true, "requires": { "debug": "^2.6.8", - "pkg-dir": "^1.0.0" + "pkg-dir": "^2.0.0" } }, "eslint-plugin-import": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz", - "integrity": "sha512-Rf7dfKJxZ16QuTgVv1OYNxkZcsu/hULFnC+e+w0Gzi6jMC3guQoWQgxYxc54IDRinlb6/0v5z/PxxIKmVctN+g==", + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.17.3.tgz", + "integrity": "sha512-qeVf/UwXFJbeyLbxuY8RgqDyEKCkqV7YC+E5S5uOjAp4tOc8zj01JP3ucoBM8JcEqd1qRasJSg6LLlisirfy0Q==", "dev": true, "requires": { - "builtin-modules": "^1.1.1", + "array-includes": "^3.0.3", "contains-path": "^0.1.0", - "debug": "^2.6.8", + "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.1.1", - "has": "^1.0.1", - "lodash.cond": "^4.3.0", - "minimatch": "^3.0.3", - "read-pkg-up": "^2.0.0" + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" }, "dependencies": { "doctrine": { @@ -3155,38 +2995,37 @@ "dev": true }, "espree": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.1.tgz", - "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "dev": true, "requires": { - "acorn": "^5.1.1", + "acorn": "^5.5.0", "acorn-jsx": "^3.0.0" } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" } }, "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "^4.1.0", - "object-assign": "^4.0.1" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -3222,13 +3061,13 @@ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" }, "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -3247,6 +3086,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, + "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -3256,66 +3096,46 @@ "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, + "optional": true, "requires": { "fill-range": "^2.1.0" } }, - "expand-tilde": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", - "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", - "dev": true, - "requires": { - "os-homedir": "^1.0.1" - } - }, "express": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", - "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { - "accepts": "~1.3.4", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.1", - "encodeurl": "~1.0.1", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.0", + "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.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.2", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.1", - "serve-static": "1.13.1", - "setprototypeof": "1.1.0", - "statuses": "~1.3.1", - "type-is": "~1.6.15", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "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", "utils-merge": "1.0.1", "vary": "~1.1.2" - }, - "dependencies": { - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - } } }, "express-list-routes": { @@ -3335,19 +3155,20 @@ } }, "express-request-id": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/express-request-id/-/express-request-id-1.4.0.tgz", - "integrity": "sha1-J3ssCUmAPmgQTJ1Fw+aJNPlr9aI=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/express-request-id/-/express-request-id-1.4.1.tgz", + "integrity": "sha512-qpxK6XhDYtdx9FvxwCHkUeZVWtkGbWR87hBAzGECfwYF/QQCPXEwwB2/9NGkOR1tT7/aLs9mma3CT0vjSzuZVw==", "requires": { - "uuid": "^3.0.1" + "uuid": "^3.3.2" } }, "express-sanitizer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/express-sanitizer/-/express-sanitizer-1.0.2.tgz", - "integrity": "sha1-In78pxSpy+M7siFY6ePocU06UnU=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/express-sanitizer/-/express-sanitizer-1.0.5.tgz", + "integrity": "sha512-48/Tf1DZ7JklRVTcXQLHAxhq4GNJTuHq2jjIYhyTmu0Bw+X06YPDD/e/tdn1QLYk706xw4N8JFxtjslRrDGb8g==", "requires": { - "sanitizer": "0.1.3" + "sanitizer": "0.1.3", + "underscore": "1.8.3" } }, "express-validation": { @@ -3359,9 +3180,9 @@ } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -3389,6 +3210,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, + "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3398,16 +3220,6 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, - "fancy-log": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", - "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", - "dev": true, - "requires": { - "chalk": "^1.1.1", - "time-stamp": "^1.0.0" - } - }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", @@ -3444,11 +3256,17 @@ "object-assign": "^4.0.1" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true + "dev": true, + "optional": true }, "fileset": { "version": "2.0.3", @@ -3461,9 +3279,9 @@ }, "dependencies": { "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3481,152 +3299,88 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "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" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - } } }, "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.1", + "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.3.1", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", "unpipe": "~1.0.0" - }, - "dependencies": { - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - } } }, - "find-index": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", - "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", - "dev": true - }, "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "findup-sync": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-1.0.0.tgz", - "integrity": "sha1-b35LV7buOkA3tEFOrt6j9Y9x4Ow=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "detect-file": "^0.1.0", - "is-glob": "^2.0.1", - "micromatch": "^2.3.7", - "resolve-dir": "^0.1.0" + "locate-path": "^2.0.0" } }, - "fined": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", - "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "dev": true, "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" + "is-buffer": "~2.0.3" }, "dependencies": { - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true } } }, - "first-chunk-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", - "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", - "dev": true - }, - "flagged-respawn": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-0.3.2.tgz", - "integrity": "sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU=", - "dev": true - }, "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", "dev": true, "requires": { "circular-json": "^0.3.1", - "del": "^2.0.2", "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", "write": "^0.2.1" - } - }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, "requires": { - "ms": "2.0.0" + "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" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" } } } @@ -3642,6 +3396,7 @@ "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, + "optional": true, "requires": { "for-in": "^1.0.1" } @@ -3652,12 +3407,12 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "version": "2.3.3", + "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.5", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, @@ -3671,9 +3426,9 @@ } }, "formidable": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", - "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" }, "forwarded": { "version": "0.1.2", @@ -3694,16 +3449,10 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, - "fs-exists-sync": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", - "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", - "dev": true - }, "fs-extra": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", - "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -3712,16 +3461,15 @@ } }, "fs-readdir-recursive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz", - "integrity": "sha1-jNF0XItPiinIyuw5JHaSG6GV9WA=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", "dev": true }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.9", @@ -3978,13 +3726,6 @@ "dev": true, "optional": true }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, "needle": { "version": "2.3.0", "bundled": true, @@ -4265,21 +4006,15 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "gaze": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", - "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "dev": true, "requires": { - "globule": "~0.1.0" + "is-property": "^1.0.2" } }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true - }, "generate-object-property": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", @@ -4289,22 +4024,20 @@ "is-property": "^1.0.0" } }, - "generic-pool": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz", - "integrity": "sha1-iGvFvwvrfblugby7oHiBjeWmJoM=" - }, "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } }, "get-value": { "version": "2.0.6", @@ -4324,6 +4057,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -4337,6 +4071,7 @@ "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, + "optional": true, "requires": { "glob-parent": "^2.0.0", "is-glob": "^2.0.0" @@ -4351,83 +4086,6 @@ "is-glob": "^2.0.0" } }, - "glob-stream": { - "version": "3.1.18", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", - "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", - "dev": true, - "requires": { - "glob": "^4.3.1", - "glob2base": "^0.0.12", - "minimatch": "^2.0.1", - "ordered-read-streams": "^0.1.0", - "through2": "^0.6.1", - "unique-stream": "^1.0.0" - }, - "dependencies": { - "glob": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", - "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^2.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", - "dev": true, - "requires": { - "brace-expansion": "^1.0.0" - } - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true, - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - } - } - }, - "glob-watcher": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", - "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", - "dev": true, - "requires": { - "gaze": "^0.5.1" - } - }, - "glob2base": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", - "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", - "dev": true, - "requires": { - "find-index": "^0.1.1" - } - }, "global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", @@ -4437,125 +4095,12 @@ "ini": "^1.3.4" } }, - "global-modules": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", - "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", - "dev": true, - "requires": { - "global-prefix": "^0.1.4", - "is-windows": "^0.2.0" - } - }, - "global-prefix": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", - "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.0", - "ini": "^1.3.4", - "is-windows": "^0.2.0", - "which": "^1.2.12" - } - }, "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "globule": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", - "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", - "dev": true, - "requires": { - "glob": "~3.1.21", - "lodash": "~1.0.1", - "minimatch": "~0.2.11" - }, - "dependencies": { - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "dev": true, - "requires": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", - "dev": true - }, - "lodash": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", - "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true, - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, - "glogg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", - "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", @@ -4573,142 +4118,45 @@ "timed-out": "^4.0.0", "unzip-response": "^2.0.1", "url-parse-lax": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", - "dev": true - }, - "gulp": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", - "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", - "dev": true, - "requires": { - "archy": "^1.0.0", - "chalk": "^1.0.0", - "deprecated": "^0.0.1", - "gulp-util": "^3.0.0", - "interpret": "^1.0.0", - "liftoff": "^2.1.0", - "minimist": "^1.1.0", - "orchestrator": "^0.3.0", - "pretty-hrtime": "^1.0.0", - "semver": "^4.1.0", - "tildify": "^1.0.0", - "v8flags": "^2.0.2", - "vinyl-fs": "^0.3.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true - } - } - }, - "gulp-help": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/gulp-help/-/gulp-help-1.6.1.tgz", - "integrity": "sha1-Jh2xhuGDl/7z9qLCLpwxW/qIrgw=", - "dev": true, - "requires": { - "chalk": "^1.0.0", - "object-assign": "^3.0.0" }, "dependencies": { - "object-assign": { + "get-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true } } }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - } - } + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true, - "requires": { - "glogg": "^1.0.0" - } + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true }, "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "dev": true, "requires": { - "async": "^1.4.0", + "neo-async": "^2.6.0", "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" }, "dependencies": { "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -4724,28 +4172,15 @@ "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - } } }, "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "^1.0.2" + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -4762,14 +4197,11 @@ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true }, "has-value": { "version": "1.0.0", @@ -4840,10 +4272,16 @@ "simple-lru-cache": "0.0.x" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" }, "home-or-tmp": { "version": "2.0.0", @@ -4855,19 +4293,10 @@ "os-tmpdir": "^1.0.1" } }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "http-aws-es": { @@ -4876,14 +4305,15 @@ "integrity": "sha512-5OJVj9/JSNOVFgIOnBK+9fwDePd35PF1odskYjp/aqstuurZy1XdmHoDP+wPE5LH9Pe/TasIJyARyH7aJnLh/A==" }, "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { - "depd": "1.1.1", + "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" } }, "http-signature": { @@ -4896,90 +4326,35 @@ "sshpk": "^1.7.0" } }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "requires": { + "ms": "^2.0.0" + } + }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "idtoken-verifier": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.2.0.tgz", - "integrity": "sha512-8jmmFHwdPz8L73zGNAXHHOV9yXNC+Z0TUBN5rafpoaFaLFltlIFr1JkQa3FYAETP23eSsulVw0sBiwrE8jqbUg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.3.1.tgz", + "integrity": "sha512-o0aplv9JqTuHz9jywi3fXXAHUWZ0nEWSjS1qBawLU74C+iqScORwBFXoac2zVoggE1hTaImikE8vALkZQu9I3Q==", "requires": { "base64-js": "^1.2.0", "crypto-js": "^3.1.9-1", + "es6-promise-polyfill": "^1.2.0", + "isomorphic-unfetch": "^3.0.0", "jsbn": "^0.1.0", - "superagent": "^3.8.2", "url-join": "^1.1.0" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "requires": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - } - }, "url-join": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", @@ -4993,9 +4368,9 @@ "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" }, "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, "ignore-by-default": { @@ -5036,9 +4411,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, "inquirer": { @@ -5063,40 +4438,30 @@ } }, "interpret": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.4.tgz", - "integrity": "sha1-ggzdWIuGj/sZGoCVBtbJyPISsbA=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", "dev": true }, "invariant": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", - "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { "loose-envify": "^1.0.0" } }, "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "ipaddr.js": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", - "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" - }, - "is-absolute": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", - "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", - "dev": true, - "requires": { - "is-relative": "^0.2.1", - "is-windows": "^0.2.0" - } + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" }, "is-accessor-descriptor": { "version": "0.1.6", @@ -5107,6 +4472,12 @@ "kind-of": "^3.0.2" } }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5122,20 +4493,22 @@ "binary-extensions": "^1.0.0" } }, + "is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true }, "is-ci": { "version": "1.2.1", @@ -5155,6 +4528,12 @@ "kind-of": "^3.0.2" } }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -5178,13 +4557,15 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true + "dev": true, + "optional": true }, "is-equal-shallow": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, + "optional": true, "requires": { "is-primitive": "^2.0.0" } @@ -5219,6 +4600,12 @@ "number-is-nan": "^1.0.0" } }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", @@ -5238,14 +4625,21 @@ "is-path-inside": "^1.0.0" } }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, "is-my-json-valid": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", - "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", + "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" } @@ -5261,6 +4655,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" } @@ -5271,25 +4666,10 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, "is-path-inside": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", - "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { "path-is-inside": "^1.0.1" @@ -5316,13 +4696,15 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true + "dev": true, + "optional": true }, "is-primitive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true + "dev": true, + "optional": true }, "is-promise": { "version": "2.1.0", @@ -5342,23 +4724,20 @@ "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, - "is-relative": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", - "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "is-unc-path": "^0.1.1" + "has": "^1.0.1" } }, "is-resolvable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", - "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", - "dev": true, - "requires": { - "tryit": "^1.0.1" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true }, "is-retry-allowed": { "version": "1.1.0", @@ -5372,20 +4751,20 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-unc-path": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", - "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", - "dev": true, - "requires": { - "unc-path-regex": "^0.1.0" - } - }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -5393,9 +4772,9 @@ "dev": true }, "is-windows": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, "isarray": { @@ -5419,6 +4798,7 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, + "optional": true, "requires": { "isarray": "1.0.0" }, @@ -5427,24 +4807,34 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true } } }, + "isomorphic-unfetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.0.0.tgz", + "integrity": "sha512-V0tmJSYfkKokZ5mgl0cmfQMTb7MLHsBMngTkbLY0eXvKqiVRRoZP04Ly+KhKrJfKtzC9E6Pp15Jo+bwh7Vi2XQ==", + "requires": { + "node-fetch": "^2.2.0", + "unfetch": "^4.0.0" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul": { - "version": "1.0.0-alpha.2", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-1.0.0-alpha.2.tgz", - "integrity": "sha1-BglrwI6Yuq10Sq5Gli2N+frGPQg=", + "version": "1.1.0-alpha.1", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-1.1.0-alpha.1.tgz", + "integrity": "sha1-eBeVZWAYohdMX2DzZ+5dNhy1e3c=", "dev": true, "requires": { "abbrev": "1.0.x", "async": "1.x", - "istanbul-api": "^1.0.0-alpha", + "istanbul-api": "^1.1.0-alpha", "js-yaml": "3.x", "mkdirp": "0.5.x", "nopt": "3.x", @@ -5479,7 +4869,75 @@ "requires": { "lodash": "^4.17.11" } - }, + } + } + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", + "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", + "dev": true, + "requires": { + "append-transform": "^0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "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" + } + }, + "istanbul-lib-report": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", + "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" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", + "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" + }, + "dependencies": { "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -5503,59 +4961,10 @@ "path-is-absolute": "^1.0.0" } }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "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-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "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" - } - }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", - "dev": true, - "requires": { - "handlebars": "^4.0.3" - } - }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "rimraf": { @@ -5566,63 +4975,16 @@ "requires": { "glob": "^7.1.3" } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "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" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true } } }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "istanbul-reports": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", + "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", "dev": true, "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", - "dev": true - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", - "dev": true - } + "handlebars": "^4.0.3" } }, "jmespath": { @@ -5647,15 +5009,42 @@ "integrity": "sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU=" }, "js-beautify": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.4.tgz", - "integrity": "sha512-6YX1g+lIl0/JDxjFFbgj7fz6i0bWFa2Hdc7PfGqFhynaEiYe1NJ3R1nda0VGaRiGU82OllR+EGDoWFpGr3k5Kg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.0.tgz", + "integrity": "sha512-OMwf/tPDpE/BLlYKqZOhqWsd3/z2N3KOlyn1wsCRGFwViE8LOQTcDtathQvHvZc+q+zWmcNAbwKSC+iJoMaH2Q==", "dev": true, "requires": { - "config-chain": "~1.1.5", - "editorconfig": "^0.13.2", - "mkdirp": "~0.5.0", - "nopt": "~3.0.1" + "config-chain": "^1.1.12", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "mkdirp": "~0.5.1", + "nopt": "~4.0.1" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "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" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } } }, "js-cookie": { @@ -5663,11 +5052,6 @@ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.0.tgz", "integrity": "sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s=" }, - "js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" - }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -5720,9 +5104,12 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz", - "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } }, "jsonfile": { "version": "4.0.0", @@ -5746,11 +5133,11 @@ "dev": true }, "jsonwebtoken": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", - "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", "requires": { - "jws": "^3.1.5", + "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -5758,13 +5145,14 @@ "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", - "ms": "^2.1.1" + "ms": "^2.1.1", + "semver": "^5.6.0" }, "dependencies": { "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -5780,12 +5168,12 @@ } }, "jwa": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", - "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", "requires": { "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.10", + "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, @@ -5802,68 +5190,19 @@ "request": "^2.88.0" }, "dependencies": { - "ecdsa-sig-formatter": { - "version": "1.0.11", - "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" - } - }, - "jsonwebtoken": { - "version": "8.5.1", - "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" - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "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" - } - }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, "jws": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", - "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", "requires": { - "jwa": "^1.1.5", + "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, @@ -5891,20 +5230,13 @@ "integrity": "sha1-fQ0U7vPslwLG8wxg6oHxqNP5APs=", "dev": true }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true - }, "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "^2.0.0" } }, "le_node": { @@ -5920,11 +5252,6 @@ "semver": "5.1.0" }, "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, "semver": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", @@ -5951,41 +5278,10 @@ "nan": "^2.10.0" }, "dependencies": { - "nan": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", - "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==" - } - } - }, - "liftoff": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.3.0.tgz", - "integrity": "sha1-qY8v9nGD2Lp8+soQVIvX/wVQs4U=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "findup-sync": "^0.4.2", - "fined": "^1.0.1", - "flagged-respawn": "^0.3.2", - "lodash.isplainobject": "^4.0.4", - "lodash.isstring": "^4.0.1", - "lodash.mapvalues": "^4.4.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "dependencies": { - "findup-sync": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.4.3.tgz", - "integrity": "sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI=", - "dev": true, - "requires": { - "detect-file": "^0.1.0", - "is-glob": "^2.0.1", - "micromatch": "^2.3.7", - "resolve-dir": "^0.1.0" - } + "bindings": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", + "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" } } }, @@ -6014,14 +5310,6 @@ "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } } }, "lock": { @@ -6034,97 +5322,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, - "lodash-compat": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/lodash-compat/-/lodash-compat-3.10.2.tgz", - "integrity": "sha1-xpQBKKnTD46QLNLPmf0Muk7PwYM=" - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "lodash.cond": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", - "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", - "dev": true - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } - }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, "lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -6150,59 +5352,55 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.mapvalues": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", - "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=", - "dev": true - }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "chalk": "^2.0.1" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "lolex": { @@ -6216,19 +5414,13 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { - "js-tokens": "^3.0.0" + "js-tokens": "^3.0.0 || ^4.0.0" } }, "lowercase-keys": { @@ -6238,10 +5430,13 @@ "dev": true }, "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true + "version": "4.0.2", + "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" + } }, "lru-memoizer": { "version": "1.12.0", @@ -6252,17 +5447,6 @@ "lodash": "^4.17.4", "lru-cache": "~4.0.0", "very-fast-args": "^1.1.0" - }, - "dependencies": { - "lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", - "requires": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - } } }, "lru-queue": { @@ -6291,6 +5475,15 @@ } } }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -6310,7 +5503,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true + "dev": true, + "optional": true }, "media-typer": { "version": "0.3.0", @@ -6318,28 +5512,30 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" } }, "memoizee": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.11.tgz", - "integrity": "sha1-vemBdmPJ5A/bKk6hw2cpYIeujI8=", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", "dev": true, "requires": { "d": "1", - "es5-ext": "^0.10.30", + "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.2" + "timers-ext": "^0.1.5" } }, "memwatch-next": { @@ -6377,6 +5573,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, + "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -6399,27 +5596,27 @@ "integrity": "sha1-bMWtOGJByrjniv+WT4cCjuyS2sU=" }, "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "requires": { - "mime-db": "~1.30.0" + "mime-db": "1.40.0" } }, "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "minimatch": { @@ -6431,9 +5628,9 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "mixin-deep": { "version": "1.3.1", @@ -6462,90 +5659,144 @@ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } } }, "mocha": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", - "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", - "dev": true, - "requires": { - "commander": "2.3.0", - "debug": "2.2.0", - "diff": "1.4.0", - "escape-string-regexp": "1.0.2", - "glob": "3.2.11", - "growl": "1.9.2", - "jade": "0.26.3", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", + "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "1.2.0", - "to-iso-string": "0.0.2" + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" }, "dependencies": { - "commander": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", - "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", - "dev": true - }, "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "0.7.1" + "ms": "^2.1.1" } }, - "escape-string-regexp": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", - "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", - "dev": true + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } }, "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", "inherits": "2", - "minimatch": "0.3" + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "supports-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", - "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "moment-timezone": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", - "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", + "version": "0.5.25", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.25.tgz", + "integrity": "sha512-DgEaTyN/z0HFaVcVbSyVCUU6HeFdnNC3vE4c9cgu2dgMTvjBUBdBzWfasTBmAW45u5OIMeCJtU8yNjM22DHucw==", "requires": { "moment": ">= 2.9.0" } @@ -6555,15 +5806,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "requires": { - "duplexer2": "0.0.2" - } - }, "murmur-hash-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/murmur-hash-js/-/murmur-hash-js-1.0.0.tgz", @@ -6587,9 +5829,9 @@ } }, "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, "nanomatch": { "version": "1.2.13", @@ -6622,12 +5864,6 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -6636,12 +5872,6 @@ } } }, - "natives": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.0.tgz", - "integrity": "sha1-6f+EFBimsux6SV6TmYT3jxY+bjE=", - "dev": true - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6655,9 +5885,15 @@ "optional": true }, "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true }, "next-tick": { "version": "1.0.0", @@ -6673,6 +5909,12 @@ "lodash": "^4.3.0" } }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "no-kafka": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/no-kafka/-/no-kafka-3.4.3.tgz", @@ -6690,6 +5932,21 @@ "wrr-pool": "^1.0.3" } }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, "nodemon": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", @@ -6800,9 +6057,9 @@ }, "dependencies": { "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -7058,12 +6315,6 @@ } } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -7103,53 +6354,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "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" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "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" - } - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -7171,13 +6375,13 @@ } }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } @@ -7238,6 +6442,12 @@ } } }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -7255,33 +6465,38 @@ } } }, - "object.defaults": { + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "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" + } + }, + "object.entries": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", "dev": true, "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" } }, "object.omit": { @@ -7289,6 +6504,7 @@ "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, + "optional": true, "requires": { "for-own": "^0.1.4", "is-extendable": "^0.1.1" @@ -7320,9 +6536,9 @@ } }, "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" }, "once": { "version": "1.4.0", @@ -7348,6 +6564,12 @@ "wordwrap": "~0.0.2" }, "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", @@ -7370,37 +6592,21 @@ "wordwrap": "~1.0.0" } }, - "orchestrator": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", - "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", - "dev": true, - "requires": { - "end-of-stream": "~0.1.5", - "sequencify": "~0.0.7", - "stream-consume": "~0.1.0" - } - }, - "ordered-read-streams": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", - "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=", - "dev": true - }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true }, "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, "os-tmpdir": { @@ -7409,6 +6615,16 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "output-file-sync": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", @@ -7420,18 +6636,33 @@ "object-assign": "^4.1.0" } }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, - "p-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", @@ -7441,6 +6672,12 @@ "p-limit": "^1.1.0" } }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "package-json": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", @@ -7454,26 +6691,16 @@ } }, "packet-reader": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.2.0.tgz", - "integrity": "sha1-gZ300BC4LV6lZx+KGjrPA5vNdwA=" - }, - "parse-filepath": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.1.tgz", - "integrity": "sha1-FZ1hVdQ5BNFsEO9piRHaHpGWm3M=", - "dev": true, - "requires": { - "is-absolute": "^0.2.3", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - } + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, + "optional": true, "requires": { "glob-base": "^0.3.0", "is-dotfile": "^1.0.0", @@ -7490,16 +6717,10 @@ "error-ex": "^1.2.0" } }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "pascalcase": { "version": "0.1.1", @@ -7514,13 +6735,10 @@ "dev": true }, "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true }, "path-is-absolute": { "version": "1.0.1", @@ -7540,24 +6758,9 @@ "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true, - "requires": { - "path-root-regex": "^0.1.0" - } - }, - "path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-to-regexp": { @@ -7580,24 +6783,23 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pg": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/pg/-/pg-4.5.7.tgz", - "integrity": "sha1-Ra4WsjcGpjRaAyed7MaveVwW0ps=", - "requires": { - "buffer-writer": "1.0.1", - "generic-pool": "2.4.2", - "js-string-escape": "1.0.1", - "packet-reader": "0.2.0", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.11.0.tgz", + "integrity": "sha512-YO4V7vCmEMGoF390LJaFaohWNKaA2ayoQOEZmiHVcAUF+YsRThpf/TaKCgSvsSE7cDm37Q/Cy3Gz41xiX/XjTw==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", "pg-connection-string": "0.1.3", - "pg-types": "1.*", - "pgpass": "0.0.3", - "semver": "^4.1.0" + "pg-pool": "^2.0.4", + "pg-types": "~2.0.0", + "pgpass": "1.x", + "semver": "4.3.2" }, "dependencies": { "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" } } }, @@ -7606,20 +6808,37 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, "pg-native": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-1.10.1.tgz", - "integrity": "sha1-lOYcy7hafzQ2suUmMVx1gRB/5Aw=", + "version": "3.0.0", + "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.6.0", + "pg-types": "^1.12.1", "readable-stream": "1.0.31" }, "dependencies": { "pg-types": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.6.0.tgz", - "integrity": "sha1-OHKg8ZkUMCVJf07ipl/a8A1+qLM=" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz", + "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": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz", + "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==" }, "readable-stream": { "version": "1.0.31", @@ -7634,23 +6853,29 @@ } } }, + "pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g==" + }, "pg-types": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz", - "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.0.1.tgz", + "integrity": "sha512-b7y6QM1VF5nOeX9ukMQ0h8a9z89mojrBHXfJeSug4mhL0YpxNBm83ot2TROyoAmX/ZOX3UbwVO4EbH7i1ZZNiw==", "requires": { - "postgres-array": "~1.0.0", + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.0", + "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "pgpass": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-0.0.3.tgz", - "integrity": "sha1-EuZ+NDsxicLzEgbrycwL7//PkUA=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", + "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", "requires": { - "split": "~0.3" + "split": "^1.0.0" } }, "pify": { @@ -7659,28 +6884,13 @@ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "^1.0.0" + "find-up": "^2.1.0" } }, "pluralize": { @@ -7696,9 +6906,9 @@ "dev": true }, "postgres-array": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", - "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" }, "postgres-bytea": { "version": "1.0.0", @@ -7706,14 +6916,14 @@ "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" }, "postgres-date": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz", - "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", + "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==" }, "postgres-interval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz", - "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==", + "version": "1.2.0", + "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" } @@ -7739,13 +6949,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true + "dev": true, + "optional": true }, "private": { "version": "0.1.8", @@ -7754,9 +6959,9 @@ "dev": true }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "1.1.8", @@ -7764,14 +6969,6 @@ "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "~2.0.3" - } - }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -7784,12 +6981,12 @@ "integrity": "sha512-Xdayp8sB/mU+sUV4G7ws8xtYMGdQnxbeIfLjyO9TZZRJdztBGhlmbI5x1qcY4TG5hBkIKGnc28i7nXxaugu88w==" }, "proxy-addr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", - "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "version": "2.0.5", + "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", - "ipaddr.js": "1.5.2" + "ipaddr.js": "1.9.0" } }, "pseudomap": { @@ -7808,34 +7005,77 @@ "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "optional": true + } + } + }, "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, @@ -7849,14 +7089,6 @@ "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "read-pkg": { @@ -7878,17 +7110,6 @@ "requires": { "find-up": "^2.0.0", "read-pkg": "^2.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - } } }, "readable-stream": { @@ -7903,54 +7124,323 @@ } }, "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, - "optional": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" - } + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, - "optional": true, "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readline2": { + "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" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "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" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "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" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "readline2": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", @@ -7988,22 +7478,16 @@ "backoff": "~2.5.0" } }, - "redefine": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/redefine/-/redefine-0.2.1.tgz", - "integrity": "sha1-6J7npvJNGf/2JZBWkzLcYDgKiaM=", - "dev": true - }, "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", "dev": true }, "regenerator-runtime": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", - "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true }, "regenerator-transform": { @@ -8034,6 +7518,7 @@ "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, + "optional": true, "requires": { "is-equal-shallow": "^0.1.3" } @@ -8113,9 +7598,9 @@ "integrity": "sha1-FJjl3wmEwn5Jt26/Boh8otARUNI=" }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true }, "repeat-string": { @@ -8133,12 +7618,6 @@ "is-finite": "^1.0.0" } }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", @@ -8166,51 +7645,10 @@ "uuid": "^3.3.2" }, "dependencies": { - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "form-data": { - "version": "2.3.3", - "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" - } - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "requires": { - "mime-db": "1.40.0" - } - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -8221,9 +7659,9 @@ "dev": true }, "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, "require-uncached": { @@ -8236,23 +7674,18 @@ "resolve-from": "^1.0.0" } }, - "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, - "resolve-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", - "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", + "resolve": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", + "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", "dev": true, "requires": { - "expand-tilde": "^1.2.2", - "global-modules": "^0.2.3" + "path-parse": "^1.0.6" } }, "resolve-from": { @@ -8284,28 +7717,18 @@ "dev": true }, "retry-as-promised": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", - "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", - "requires": { - "bluebird": "^3.4.6", - "debug": "^2.6.9" - } - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", "requires": { - "align-text": "^0.1.1" + "any-promise": "^1.3.0" } }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, "requires": { "glob": "^6.0.1" } @@ -8326,14 +7749,14 @@ "dev": true }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-json-stringify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz", - "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true }, "safe-regex": { @@ -8367,9 +7790,9 @@ "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" }, "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" }, "semver-diff": { "version": "2.1.0", @@ -8381,183 +7804,102 @@ } }, "send": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.1", + "depd": "~1.1.2", "destroy": "~1.0.4", - "encodeurl": "~1.0.1", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.3.1" - }, - "dependencies": { - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - } - } - }, - "sequelize": { - "version": "3.30.4", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-3.30.4.tgz", - "integrity": "sha1-vaLfHjGFSwmeQUmhEen8Clyh0aQ=", - "requires": { - "bluebird": "^3.3.4", - "depd": "^1.1.0", - "dottie": "^1.0.0", - "generic-pool": "2.4.2", - "inflection": "^1.6.0", - "lodash": "4.12.0", - "moment": "^2.13.0", - "moment-timezone": "^0.5.4", - "retry-as-promised": "^2.0.0", - "semver": "^5.0.1", - "shimmer": "1.1.0", - "terraformer-wkt-parser": "^1.1.0", - "toposort-class": "^1.0.1", - "uuid": "^3.0.0", - "validator": "^5.2.0", - "wkx": "0.2.0" - }, - "dependencies": { - "lodash": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.12.0.tgz", - "integrity": "sha1-K9bcRqBA9Z5obJcu0h2T3FkFMlg=" - } - } - }, - "sequelize-cli": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-2.8.0.tgz", - "integrity": "sha1-QwTM5g5JkWlgP4ON7bq0IcmEnnQ=", - "dev": true, - "requires": { - "bluebird": "^3.5.0", - "cli-color": "~1.2.0", - "findup-sync": "^1.0.0", - "fs-extra": "^4.0.1", - "gulp": "^3.9.1", - "gulp-help": "~1.6.1", - "js-beautify": "^1.6.11", - "lodash": "^4.17.4", - "moment": "^2.17.1", - "resolve": "^1.3.3", - "umzug": "^1.12.0", - "yargs": "^8.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "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" - } - } - } - }, - "string-width": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.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" + }, + "dependencies": { + "ms": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "sequelize": { + "version": "5.8.7", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.8.7.tgz", + "integrity": "sha512-1rubZM8fAyCt5ipyS+3HJ3Jbmb8WesLdPJ3jIbTD+78EbuPZILFEA5fK0mliVRBx7oM7oPULeVX0lxSRXBV1jw==", + "requires": { + "bluebird": "^3.5.0", + "cls-bluebird": "^2.1.0", + "debug": "^4.1.1", + "dottie": "^2.0.0", + "inflection": "1.12.0", + "lodash": "^4.17.11", + "moment": "^2.24.0", + "moment-timezone": "^0.5.21", + "retry-as-promised": "^3.1.0", + "semver": "^5.6.0", + "sequelize-pool": "^1.0.2", + "toposort-class": "^1.0.1", + "uuid": "^3.2.1", + "validator": "^10.11.0", + "wkx": "^0.4.6" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } + "ms": "^2.1.1" } }, - "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", - "dev": true, - "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" - } + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, - "sequencify": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", - "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", - "dev": true + "sequelize-cli": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.5.0.tgz", + "integrity": "sha512-twVQ02alCpr2XvxNmpi32C48WZs6xHTH1OFTfTS5Meg3BVqOM8ghiZoml4FITFjlD8sAJSQjlAHTwqTbuolA6Q==", + "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" + } + }, + "sequelize-pool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-1.0.2.tgz", + "integrity": "sha512-VMKl/gCCdIvB1gFZ7p+oqLFEyZEz3oMMYjkKvfEC7GoO9bBcxmfOOU9RdkoltfXGgBZFigSChihRly2gKtsh2w==", + "requires": { + "bluebird": "^3.5.3" + } }, "serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "requires": { - "encodeurl": "~1.0.1", + "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.1" + "parseurl": "~1.3.3", + "send": "0.17.1" } }, "set-blocking": { @@ -8566,13 +7908,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true, - "optional": true - }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -8597,9 +7932,9 @@ } }, "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "shebang-command": { "version": "1.2.0", @@ -8628,9 +7963,9 @@ }, "dependencies": { "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -8644,9 +7979,9 @@ } }, "shimmer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz", - "integrity": "sha1-l9c3cTf/u6tCVSLkKf4KqJpIizU=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" }, "sigmund": { "version": "1.0.1", @@ -8690,12 +8025,12 @@ "dev": true }, "sleep": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/sleep/-/sleep-5.1.1.tgz", - "integrity": "sha1-h4+h1E0I7rDyb7IBjvhinrGjq5Q=", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/sleep/-/sleep-5.2.4.tgz", + "integrity": "sha512-SoltvxayTifWOgOGD6CTh+djcp5TaOa/zdbaA38wEH1ahF2azmiLOh8CPt6ExHf0pAJAsA9OCHTS7zK24Ym4yA==", "dev": true, "requires": { - "nan": ">=2.5.1" + "nan": ">=2.12.1" } }, "slice-ansi": { @@ -8846,37 +8181,42 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, - "sparkles": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", - "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", - "dev": true - }, "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { - "spdx-license-ids": "^1.0.2" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "dev": true }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", "dev": true }, "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "requires": { "through": "2" } @@ -8893,8 +8233,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", @@ -8934,14 +8273,9 @@ } }, "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - }, - "stream-consume": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz", - "integrity": "sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8=" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "string-width": { "version": "1.0.2", @@ -8986,28 +8320,28 @@ "dev": true }, "superagent": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.0.tgz", - "integrity": "sha512-71XGWgtn70TNwgmgYa69dPOYg55aU9FCahjUNY03rOrKvaTCaU3b9MeZmqonmf9Od96SCxr3vGfEAnhM7dtxCw==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", "requires": { "component-emitter": "^1.2.0", "cookiejar": "^2.1.0", "debug": "^3.1.0", "extend": "^3.0.0", "form-data": "^2.3.1", - "formidable": "^1.1.1", + "formidable": "^1.2.0", "methods": "^1.1.1", "mime": "^1.4.1", "qs": "^6.5.1", - "readable-stream": "^2.0.5" + "readable-stream": "^2.3.5" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "isarray": { @@ -9015,24 +8349,29 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", + "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -9045,74 +8384,13 @@ "integrity": "sha1-5Js1ypbA47HQ4/SWBRNt8OCgKLc=" }, "supertest": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-2.0.1.tgz", - "integrity": "sha1-oFgIHXiPFRXUcA11Aogea3WeRM0=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", + "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", "dev": true, "requires": { - "methods": "1.x", - "superagent": "^2.0.0" - }, - "dependencies": { - "form-data": { - "version": "1.0.0-rc4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz", - "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=", - "dev": true, - "requires": { - "async": "^1.5.2", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.10" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "superagent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-2.3.0.tgz", - "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=", - "dev": true, - "requires": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.0.6", - "debug": "^2.2.0", - "extend": "^3.0.0", - "form-data": "1.0.0-rc4", - "formidable": "^1.0.17", - "methods": "^1.1.1", - "mime": "^1.3.4", - "qs": "^6.1.0", - "readable-stream": "^2.0.5" - } - } + "methods": "^1.1.2", + "superagent": "^3.8.3" } }, "supports-color": { @@ -9120,6 +8398,19 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, + "swagger-ui-dist": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.22.3.tgz", + "integrity": "sha512-tmjAsqT43pqg5UoiQ2805c+juX0ASSoI/Ash/0c19jjAOFtTfE93ZrzmFd9hjqVgre935CYeXT0uaku42Lu8xg==" + }, + "swagger-ui-express": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.0.6.tgz", + "integrity": "sha512-7YkBfVWRYjvnGITs7vygM8VNF91iUwX8ReHQaTD9I7q8Ky8SYXZ81gyFc+kovE34iSbCC1yW+n2oII3b3uSxXQ==", + "requires": { + "swagger-ui-dist": "^3.18.1" + } + }, "table": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", @@ -9134,6 +8425,16 @@ "string-width": "^2.0.0" }, "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -9168,11 +8469,11 @@ } }, "tc-core-library-js": { - "version": "github:appirio-tech/tc-core-library-js#d16413db30b1eed21c0cf426e185bedb2329ddab", - "from": "github:appirio-tech/tc-core-library-js#v2.6", + "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.12.0", + "axios": "^0.19.0", "bunyan": "^1.8.12", "jsonwebtoken": "^8.3.0", "jwks-rsa": "^1.3.0", @@ -9180,30 +8481,6 @@ "lodash": "^4.17.10", "millisecond": "^0.1.2", "request": "^2.88.0" - }, - "dependencies": { - "axios": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.12.0.tgz", - "integrity": "sha1-uQewIhzDTsHJ+sGOx/B935V4W6Q=", - "requires": { - "follow-redirects": "0.0.7" - } - }, - "follow-redirects": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz", - "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk=", - "requires": { - "debug": "^2.2.0", - "stream-consume": "^0.1.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } } }, "term-size": { @@ -9213,92 +8490,53 @@ "dev": true, "requires": { "execa": "^0.7.0" - } - }, - "terraformer": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.8.tgz", - "integrity": "sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM=", - "requires": { - "@types/geojson": "^1.0.0" - } - }, - "terraformer-wkt-parser": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.1.2.tgz", - "integrity": "sha1-M2oMj8gglKWv+DKI9prt7NNpvww=", - "requires": { - "terraformer": "~1.0.5" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true, - "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "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" } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true } } }, - "tildify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", - "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, "timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", @@ -9306,12 +8544,12 @@ "dev": true }, "timers-ext": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz", - "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", "dev": true, "requires": { - "es5-ext": "~0.10.14", + "es5-ext": "~0.10.46", "next-tick": "1" } }, @@ -9321,12 +8559,6 @@ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", "dev": true }, - "to-iso-string": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", - "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", - "dev": true - }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -9369,6 +8601,11 @@ } } }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, "topo": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", @@ -9429,12 +8666,6 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, - "tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", - "dev": true - }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -9464,12 +8695,12 @@ "dev": true }, "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.15" + "mime-types": "~2.1.24" } }, "typedarray": { @@ -9479,43 +8710,47 @@ "dev": true }, "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", + "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", "dev": true, "optional": true, "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true + "commander": "~2.20.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } }, "umzug": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/umzug/-/umzug-1.12.0.tgz", - "integrity": "sha1-p5yR8oYu7jEwxsNH8rkK1opm6Lg=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.2.0.tgz", + "integrity": "sha512-xZLW76ax70pND9bx3wqwb8zqkFGzZIK8dIHD9WdNy/CrNfjWcwQgQkGCuUqcuwEBvUm+g07z+qWvY+pxDmMEEw==", "dev": true, "requires": { - "bluebird": "^3.4.1", - "lodash": "^4.17.0", - "moment": "^2.16.0", - "redefine": "^0.2.0", - "resolve": "^1.0.0" + "babel-runtime": "^6.23.0", + "bluebird": "^3.5.3" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } } }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", @@ -9525,6 +8760,16 @@ "debug": "^2.2.0" } }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "unfetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz", + "integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==" + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -9560,12 +8805,6 @@ } } }, - "unique-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", - "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=", - "dev": true - }, "unique-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", @@ -9576,9 +8815,9 @@ } }, "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, "unpipe": { @@ -9740,6 +8979,15 @@ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=" }, + "url-parse": { + "version": "1.4.7", + "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" + } + }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -9770,20 +9018,16 @@ "dev": true }, "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.0.tgz", + "integrity": "sha512-pPSOFl7VLhZ7LO/SFABPraZEEurkJUWSMn3MuA/r3WQZc+Z1fqou2JqLSOZbCLl73EUIxuUVX8X4jkX2vfJeAA==", "dev": true, "requires": { - "inherits": "2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } + "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" } }, "util-deprecate": { @@ -9811,19 +9055,19 @@ } }, "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "~1.0.0", - "spdx-expression-parse": "~1.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "validator": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz", - "integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw=" + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" }, "vary": { "version": "1.1.2", @@ -9845,104 +9089,10 @@ "resolved": "https://registry.npmjs.org/very-fast-args/-/very-fast-args-1.1.0.tgz", "integrity": "sha1-4W0dH6+KbllqJGQh/ZCneWPQs5Y=" }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - }, - "dependencies": { - "clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", - "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", - "dev": true - } - } - }, - "vinyl-fs": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", - "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", - "dev": true, - "requires": { - "defaults": "^1.0.0", - "glob-stream": "^3.1.5", - "glob-watcher": "^0.0.6", - "graceful-fs": "^3.0.0", - "mkdirp": "^0.5.0", - "strip-bom": "^1.0.0", - "through2": "^0.6.1", - "vinyl": "^0.4.0" - }, - "dependencies": { - "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true - }, - "graceful-fs": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "dev": true, - "requires": { - "natives": "^1.1.0" - } - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "strip-bom": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", - "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", - "dev": true, - "requires": { - "first-chunk-stream": "^1.0.0", - "is-utf8": "^0.2.0" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true, - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - }, - "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", - "dev": true, - "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" - } - } - } - }, "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -9954,6 +9104,15 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", @@ -10001,17 +9160,13 @@ "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.2.1.tgz", "integrity": "sha512-QrG9q+ObfmZBxScv0HSCqFm/owcgyR5Sgpiy1NlCZPpFXhbsmNHhTiLWoogItdBUi0fnU7Io/5ABEqRta5/6Dw==" }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, "wkx": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.2.0.tgz", - "integrity": "sha1-dsJPFqzQzY+TzTSqMx4PeWElboQ=" + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.7.tgz", + "integrity": "sha512-pHf546L96TK8RradLt1cWaIffstgv/zXZ14CGz5KnBs1AxBX0wm+IDphjJw0qrEqRv8P9W9CdTt8Z1unMRZ19A==", + "requires": { + "@types/node": "*" + } }, "wordwrap": { "version": "1.0.0", @@ -10088,9 +9243,9 @@ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, "yallist": { @@ -10098,33 +9253,262 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, + "yamljs": { + "version": "0.3.0", + "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" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "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" + } + } + } + }, "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", "dev": true, - "optional": true, "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "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.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "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" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" }, "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } } diff --git a/package.json b/package.json index 9ec62ec8..241eb814 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "start": "node dist", "startKafkaConsumers": "npm run -s build && node dist/index-kafka.js", "start:dev": "NODE_ENV=development PORT=8001 nodemon -w src --exec \"babel-node src --presets es2015\" | ./node_modules/.bin/bunyan", - "test": "NODE_ENV=test npm run lint && NODE_ENV=test npm run sync:es && NODE_ENV=test npm run sync:db && NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- --timeout 10000 --compilers js:babel-core/register $(find src -path '*spec.js*')", - "test:watch": "NODE_ENV=test ./node_modules/.bin/mocha -w --compilers js:babel-core/register $(find src -path '*spec.js*')", + "test": "NODE_ENV=test npm run lint && NODE_ENV=test npm run sync:es && NODE_ENV=test npm run sync:db && NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- --timeout 10000 --require babel-core/register $(find src -path '*spec.js*') --exit", + "test:watch": "NODE_ENV=test ./node_modules/.bin/mocha -w --require babel-core/register $(find src -path '*spec.js*')", "seed": "babel-node src/tests/seed.js --presets es2015", "demo-data": "babel-node local/seed" }, @@ -45,7 +45,7 @@ "config": "^1.20.1", "continuation-local-storage": "^3.1.7", "cors": "^2.8.4", - "elasticsearch": "^11.0.1", + "elasticsearch": "^16.1.1", "express": "^4.13.4", "express-list-routes": "^0.1.4", "express-request-id": "^1.1.0", @@ -59,15 +59,17 @@ "method-override": "^2.3.9", "moment": "^2.22.2", "no-kafka": "^3.4.3", - "pg": "^4.5.5", - "pg-native": "^1.10.1", - "sequelize": "^3.23.0", - "tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6", + "pg": "^7.11.0", + "pg-native": "^3.0.0", + "sequelize": "^5.8.7", + "swagger-ui-express": "^4.0.6", + "tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6.3", "traverse": "^0.6.6", - "urlencode": "^1.1.0" + "urlencode": "^1.1.0", + "yamljs": "^0.3.0" }, "devDependencies": { - "babel-cli": "^6.9.0", + "babel-cli": "^6.26.0", "babel-core": "^6.26.3", "babel-eslint": "^7.1.1", "babel-plugin-add-module-exports": "^0.2.1", @@ -79,14 +81,14 @@ "eslint": "^3.16.1", "eslint-config-airbnb-base": "^11.1.0", "eslint-plugin-import": "^2.2.0", - "istanbul": "^1.0.0-alpha.2", - "mocha": "^2.5.3", + "istanbul": "^1.1.0-alpha.1", + "mocha": "^6.1.4", "nodemon": "^1.19.1", "really-need": "^1.9.2", - "sequelize-cli": "^2.4.0", + "sequelize-cli": "^5.5.0", "sinon": "^1.17.4", "sinon-chai": "^2.8.0", "sleep": "^5.1.0", - "supertest": "^2.0.0" + "supertest": "^4.0.2" } } diff --git a/src/app.js b/src/app.js index ff6d807c..42677871 100644 --- a/src/app.js +++ b/src/app.js @@ -8,6 +8,8 @@ import cors from 'cors'; import coreLib from 'tc-core-library-js'; import expressRequestId from 'express-request-id'; import memWatch from 'memwatch-next'; +import swaggerUi from 'swagger-ui-express'; +import YAML from 'yamljs'; import performanceRequestLogger from './middlewares/performanceRequestLogger'; import router from './routes'; import permissions from './permissions'; @@ -15,6 +17,8 @@ import models from './models'; import analytics from './events/analytics'; import busApi from './events/busApi'; +const swaggerDocument = YAML.load('./docs/swagger.yaml'); + const app = express(); // allows overriding HTTP Method @@ -130,6 +134,8 @@ busApi(app, logger); // require('app/permissions')() permissions(); +app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); + // ======================== // Routes // ======================== @@ -137,6 +143,7 @@ permissions(); app.use(router); app.routerRef = router; + // ======================= // Initialize services // ======================= diff --git a/src/constants.js b/src/constants.js index d2b7ee41..a9ea439d 100644 --- a/src/constants.js +++ b/src/constants.js @@ -77,18 +77,29 @@ export const EVENT = { PROJECT_MEMBER_INVITE_CREATED: 'project.member.invite.created', PROJECT_MEMBER_INVITE_UPDATED: 'project.member.invite.updated', + + // project metadata + PROJECT_METADATA_CREATE: 'project.metadata.create', + PROJECT_METADATA_UPDATE: 'project.metadata.update', + PROJECT_METADATA_DELETE: 'project.metadata.delete', + + // project template + PROJECT_TEMPLATE_CREATED: 'project.template.created', + PROJECT_TEMPLATE_UPDATED: 'project.template.updated', + PROJECT_TEMPLATE_DELETED: 'project.template.deleted', }, }; export const BUS_API_EVENT = { - PROJECT_CREATED: 'notifications.connect.project.created', - PROJECT_UPDATED: 'connect.action.project.updated', - PROJECT_SUBMITTED_FOR_REVIEW: 'notifications.connect.project.submittedForReview', - PROJECT_APPROVED: 'notifications.connect.project.approved', - PROJECT_PAUSED: 'notifications.connect.project.paused', - PROJECT_COMPLETED: 'notifications.connect.project.completed', - PROJECT_CANCELED: 'notifications.connect.project.canceled', - PROJECT_ACTIVE: 'notifications.connect.project.active', + PROJECT_CREATED: 'project.notification.create', // 'notifications.connect.project.created', + PROJECT_UPDATED: 'project.notification.update', // 'connect.action.project.updated', + PROJECT_DELETED: 'project.notification.delete', // 'project deleted' + PROJECT_SUBMITTED_FOR_REVIEW: 'project.notification.update', // 'notifications.connect.project.submittedForReview', + PROJECT_APPROVED: 'project.notification.update', // 'notifications.connect.project.approved', + PROJECT_PAUSED: 'project.notification.update', // 'notifications.connect.project.paused', + PROJECT_COMPLETED: 'project.notification.update', // 'notifications.connect.project.completed', + PROJECT_CANCELED: 'project.notification.update', // 'notifications.connect.project.canceled', + PROJECT_ACTIVE: 'project.notification.update', // 'notifications.connect.project.active', PROJECT_PHASE_TRANSITION_ACTIVE: 'notifications.connect.project.phase.transition.active', PROJECT_PHASE_TRANSITION_COMPLETED: 'notifications.connect.project.phase.transition.completed', @@ -147,6 +158,11 @@ export const BUS_API_EVENT = { PROJECT_MEMBER_INVITE_APPROVED: 'notifications.connect.project.member.invite.approved', PROJECT_MEMBER_INVITE_REJECTED: 'notifications.connect.project.member.invite.rejected', PROJECT_MEMBER_EMAIL_INVITE_CREATED: 'connect.action.email.project.member.invite.created', + + // metadata + PROJECT_METADATA_CREATE: 'project.notification.create', + PROJECT_METADATA_UPDATE: 'project.notification.update', + PROJECT_METADATA_DELETE: 'project.notification.delete', }; export const REGEX = { @@ -176,3 +192,18 @@ export const INVITE_STATUS = { REQUEST_APPROVED: 'request_approved', CANCELED: 'canceled', }; + +export const RESOURCES = { + PROJECT: 'project', + PROJECT_TEMPLATE: 'project.template', + PROJECT_TYPE: 'project.type', + ORG_CONFIG: 'project.orgConfig', + FORM_VERSION: 'project.form.version', + FORM_REVISION: 'project.form.revision', + PRICE_CONFIG_VERSION: 'project.priceConfig.version', + PRICE_CONFIG_REVISION: 'project.priceConfig.revision', + PLAN_CONFIG_VERSION: 'project.planConfig.version', + PLAN_CONFIG_REVISION: 'project.planConfig.revision', + PRODUCT_TEMPLATE: 'product.template', + PRODUCT_CATEGORY: 'product.category', +}; diff --git a/src/events/busApi.js b/src/events/busApi.js index b1b69dc2..e07be462 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -7,19 +7,6 @@ import { createEvent } from '../services/busApi'; import models from '../models'; import getTopcoderProjectMembers from '../util'; -/** - * Map of project status and event name sent to bus api - */ -const mapEventTypes = { - [PROJECT_STATUS.DRAFT]: BUS_API_EVENT.PROJECT_CREATED, - [PROJECT_STATUS.IN_REVIEW]: BUS_API_EVENT.PROJECT_SUBMITTED_FOR_REVIEW, - [PROJECT_STATUS.REVIEWED]: BUS_API_EVENT.PROJECT_APPROVED, - [PROJECT_STATUS.COMPLETED]: BUS_API_EVENT.PROJECT_COMPLETED, - [PROJECT_STATUS.CANCELLED]: BUS_API_EVENT.PROJECT_CANCELED, - [PROJECT_STATUS.PAUSED]: BUS_API_EVENT.PROJECT_PAUSED, - [PROJECT_STATUS.ACTIVE]: BUS_API_EVENT.PROJECT_ACTIVE, -}; - /** * Builds the connect project attachment url for the given project and attachment ids. * @@ -45,74 +32,63 @@ module.exports = (app, logger) => { /** * PROJECT_DRAFT_CREATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED, ({ req, project }) => { + app.on(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED, ({ req, project }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_DRAFT_CREATED event'); // send event to bus api - createEvent(BUS_API_EVENT.PROJECT_CREATED, { - projectId: project.id, - projectName: project.name, + createEvent(BUS_API_EVENT.PROJECT_CREATED, _.assign(project, { refCode: _.get(project, 'details.utm.code'), projectUrl: connectProjectUrl(project.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); + }), logger); }); /** * PROJECT_UPDATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_UPDATED, ({ req, original, updated }) => { + app.on(EVENT.ROUTING_KEY.PROJECT_UPDATED, ({ req, updated }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_UPDATED event'); - if (original.status !== updated.status) { - logger.debug(`project status is updated from ${original.status} to ${updated.status}`); - createEvent(mapEventTypes[updated.status], { - projectId: updated.id, - projectName: updated.name, - refCode: _.get(updated, 'details.utm.code'), - projectUrl: connectProjectUrl(updated.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } else if ( - !_.isEqual(original.details, updated.details) || - !_.isEqual(original.name, updated.name) || - !_.isEqual(original.description, updated.description)) { - logger.debug('project spec is updated'); - createEvent(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED, { - projectId: updated.id, - projectName: updated.name, - refCode: _.get(updated, 'details.utm.code'), - projectUrl: connectProjectUrl(updated.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } else if (!_.isEqual(original.bookmarks, updated.bookmarks)) { - logger.debug('project bookmarks is updated'); - createEvent(BUS_API_EVENT.PROJECT_LINK_CREATED, { - projectId: updated.id, - projectName: updated.name, - refCode: _.get(updated, 'details.utm.code'), - projectUrl: connectProjectUrl(updated.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } + createEvent(BUS_API_EVENT.PROJECT_UPDATED, _.assign(updated, { + refCode: _.get(updated, 'details.utm.code'), + projectUrl: connectProjectUrl(updated.id), + }), logger); + }); - // send PROJECT_UPDATED Kafka message when one of the specified below properties changed - const watchProperties = ['status', 'details', 'name', 'description', 'bookmarks']; - if (!_.isEqual(_.pick(original, watchProperties), - _.pick(updated, watchProperties))) { - createEvent(BUS_API_EVENT.PROJECT_UPDATED, { - projectId: updated.id, - projectName: updated.name, - refCode: _.get(updated, 'details.utm.code'), - projectUrl: connectProjectUrl(updated.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } + /** + * PROJECT_DELETED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_DELETED, ({ req, project }) => { // eslint-disable-line no-unused-vars + logger.debug('receive PROJECT_DELETED event'); + + createEvent(BUS_API_EVENT.PROJECT_DELETED, project, logger); + }); + + /** + * PROJECT_METADATA_CREATE + */ + app.on(EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive PROJECT_METADATA_CREATE event'); + + // send event to bus api + createEvent(BUS_API_EVENT.PROJECT_METADATA_CREATE, resource, logger); + }); + + /** + * PROJECT_METADATA_UPDATE + */ + app.on(EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive PROJECT_METADATA_UPDATE event'); + + createEvent(BUS_API_EVENT.PROJECT_METADATA_UPDATE, resource, logger); + }); + + /** + * PROJECT_METADATA_DELETE + */ + app.on(EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive PROJECT_METADATA_DELETE event'); + + createEvent(BUS_API_EVENT.PROJECT_METADATA_DELETE, resource, logger); }); /** diff --git a/src/events/projects/index.js b/src/events/projects/index.js index 5bd0e150..ab53c704 100644 --- a/src/events/projects/index.js +++ b/src/events/projects/index.js @@ -159,7 +159,7 @@ async function projectUpdatedKafkaHandler(app, topic, payload) { // Find project by id and update activity. Single update is used as there is no need to wrap it into transaction const projectId = payload.projectId; - const project = await models.Project.findById(projectId); + const project = await models.Project.findByPk(projectId); if (!project) { throw new Error(`Project with id ${projectId} not found`); } diff --git a/src/events/projects/index.spec.js b/src/events/projects/index.spec.js index 9067166e..2b149f84 100644 --- a/src/events/projects/index.spec.js +++ b/src/events/projects/index.spec.js @@ -131,7 +131,7 @@ describe('projectUpdatedKafkaHandler', () => { it('should update lastActivityAt and lastActivityUserId columns in db', async () => { await projectUpdatedKafkaHandler(mockedApp, topic, validPayload); - const updatedProject = await models.Project.findById(project.id); + const updatedProject = await models.Project.findByPk(project.id); expect(updatedProject.lastActivityUserId).to.be.eql('2'); expect(updatedProject.lastActivityAt).to.be.greaterThan(project.lastActivityAt); }); diff --git a/src/middlewares/fieldLookupValidation.js b/src/middlewares/fieldLookupValidation.js index 73d07a9c..d1fafb15 100644 --- a/src/middlewares/fieldLookupValidation.js +++ b/src/middlewares/fieldLookupValidation.js @@ -23,7 +23,7 @@ export default function (model, modelKey, path, errorEntityName) { next(); } else { const err = new Error(`${errorEntityName} not found for key "${value}"`); - err.status = 422; + err.status = 400; next(err); } }); diff --git a/src/middlewares/validateMilestoneTemplate.js b/src/middlewares/validateMilestoneTemplate.js index 1aea3ace..3ed55ab1 100644 --- a/src/middlewares/validateMilestoneTemplate.js +++ b/src/middlewares/validateMilestoneTemplate.js @@ -18,11 +18,12 @@ async function validateReference(sourceObject) { id: sourceObject.referenceId, deletedAt: { $eq: null }, }, + raw: true, }); if (!productTemplate) { const apiErr = new Error( `Product template not found for product template id ${sourceObject.referenceId}`); - apiErr.status = 422; + apiErr.status = 400; throw apiErr; } } @@ -78,27 +79,27 @@ const validateMilestoneTemplate = { if (!util.isValidFilter(filter, ['reference', 'referenceId'])) { const apiErr = new Error('Only allowed to filter by reference and referenceId'); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } // Verify required filters are present if (!filter.reference || !filter.referenceId) { const apiErr = new Error('Please provide reference and referenceId filter parameters'); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } // Verify reference is a valid value if (!_.includes(MILESTONE_TEMPLATE_REFERENCES, filter.reference)) { const apiErr = new Error(`reference filter must be in ${MILESTONE_TEMPLATE_REFERENCES}`); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } if (_.lt(filter.referenceId, 1)) { const apiErr = new Error('referenceId filter must be a positive integer'); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } @@ -117,7 +118,7 @@ const validateMilestoneTemplate = { */ // eslint-disable-next-line valid-jsdoc validateIdParam: (req, res, next) => { - models.MilestoneTemplate.findById(req.params.milestoneTemplateId) + models.MilestoneTemplate.findByPk(req.params.milestoneTemplateId) .then((milestoneTemplate) => { if (!milestoneTemplate) { const apiErr = new Error( diff --git a/src/middlewares/validateTimeline.js b/src/middlewares/validateTimeline.js index 60cfbb4c..6cebe413 100644 --- a/src/middlewares/validateTimeline.js +++ b/src/middlewares/validateTimeline.js @@ -27,7 +27,7 @@ async function validateReference(sourceObject, req, validateProjectExists) { }); if (!project) { const apiErr = new Error(`Project not found for project id ${req.params.projectId}`); - apiErr.status = 422; + apiErr.status = 400; throw apiErr; } } @@ -45,7 +45,7 @@ async function validateReference(sourceObject, req, validateProjectExists) { }); if (!product) { const apiErr = new Error(`Product not found for product id ${sourceObject.referenceId}`); - apiErr.status = 422; + apiErr.status = 400; throw apiErr; } @@ -63,7 +63,7 @@ async function validateReference(sourceObject, req, validateProjectExists) { }); if (!phase) { const apiErr = new Error(`Phase not found for phase id ${sourceObject.referenceId}`); - apiErr.status = 422; + apiErr.status = 400; throw apiErr; } @@ -107,27 +107,27 @@ const validateTimeline = { if (!util.isValidFilter(filter, ['reference', 'referenceId'])) { const apiErr = new Error('Only allowed to filter by reference and referenceId'); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } // Verify required filters are present if (!filter.reference || !filter.referenceId) { const apiErr = new Error('Please provide reference and referenceId filter parameters'); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } // Verify reference is a valid value if (!_.includes(TIMELINE_REFERENCES, filter.reference)) { const apiErr = new Error(`reference filter must be in ${TIMELINE_REFERENCES}`); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } if (_.lt(filter.referenceId, 1)) { const apiErr = new Error('referenceId filter must be a positive integer'); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } @@ -146,7 +146,7 @@ const validateTimeline = { */ // eslint-disable-next-line valid-jsdoc validateTimelineIdParam: (req, res, next) => { - models.Timeline.findById(req.params.timelineId) + models.Timeline.findByPk(req.params.timelineId) .then((timeline) => { if (!timeline) { const apiErr = new Error(`Timeline not found for timeline id ${req.params.timelineId}`); diff --git a/src/models/index.js b/src/models/index.js index 59c8c9d2..0acc3b27 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -11,8 +11,24 @@ pg.defaults.parseInt8 = true; delete pg.native; Sequelize.cls = cls.createNamespace('tc.micro.service'); +// Sequelize.useCLS(cls.createNamespace('tc.micro.service')); + +const Op = Sequelize.Op; +const operatorsAliases = { + $gt: Op.gt, + $gte: Op.gte, + $lt: Op.lt, + $between: Op.between, + $eq: Op.eq, + $ne: Op.ne, + $or: Op.or, + $and: Op.and, + $in: Op.in, + $contains: Op.contains, +}; const sequelize = new Sequelize(config.get('dbConfig.masterUrl'), { + operatorsAliases, logging: false, dialectOptions: { ssl: false, diff --git a/src/models/milestone.js b/src/models/milestone.js index 76246a52..1f01ae70 100644 --- a/src/models/milestone.js +++ b/src/models/milestone.js @@ -36,53 +36,52 @@ module.exports = (sequelize, DataTypes) => { updatedAt: 'updatedAt', createdAt: 'createdAt', deletedAt: 'deletedAt', - classMethods: { - /** - * Get total duration of the given timeline by summing up individual milestone durations - * @param timelineId the id of timeline - */ - getTimelineDuration(timelineId) { - const where = { timelineId, hidden: false }; - return this.findAll({ - where, - order: [['order', 'asc']], - attributes: ['id', 'duration', 'startDate', 'endDate', 'actualStartDate', 'completionDate'], - raw: true, - }) - .then((milestones) => { - let scheduledDuration = 0; - let completedDuration = 0; - let duration = 0; - let progress = 0; - if (milestones) { - const fMilestone = milestones[0]; - const lMilestone = milestones[milestones.length - 1]; - const startDate = fMilestone.actualStartDate ? fMilestone.actualStartDate : fMilestone.startDate; - const endDate = lMilestone.completionDate ? lMilestone.completionDate : lMilestone.endDate; - duration = moment.utc(endDate).diff(moment.utc(startDate), 'days') + 1; - milestones.forEach((m) => { - if (m.completionDate !== null) { - let mDuration = 0; - if (m.actualStartDate !== null) { - mDuration = moment.utc(m.completionDate).diff(moment.utc(m.actualStartDate), 'days') + 1; - } else { - mDuration = moment.utc(m.completionDate).diff(moment.utc(m.startDate), 'days') + 1; - } - scheduledDuration += mDuration; - completedDuration += mDuration; - } else { - scheduledDuration += m.duration; - } - }); - if (scheduledDuration > 0) { - progress = Math.round((completedDuration / scheduledDuration) * 100); + }); + + /** + * Get total duration of the given timeline by summing up individual milestone durations + * @param timelineId the id of timeline + */ + Milestone.getTimelineDuration = (timelineId) => { + const where = { timelineId, hidden: false }; + return Milestone.findAll({ + where, + order: [['order', 'asc']], + attributes: ['id', 'duration', 'startDate', 'endDate', 'actualStartDate', 'completionDate'], + raw: true, + }) + .then((milestones) => { + let scheduledDuration = 0; + let completedDuration = 0; + let duration = 0; + let progress = 0; + if (milestones) { + const fMilestone = milestones[0]; + const lMilestone = milestones[milestones.length - 1]; + const startDate = fMilestone.actualStartDate ? fMilestone.actualStartDate : fMilestone.startDate; + const endDate = lMilestone.completionDate ? lMilestone.completionDate : lMilestone.endDate; + duration = moment.utc(endDate).diff(moment.utc(startDate), 'days') + 1; + milestones.forEach((m) => { + if (m.completionDate !== null) { + let mDuration = 0; + if (m.actualStartDate !== null) { + mDuration = moment.utc(m.completionDate).diff(moment.utc(m.actualStartDate), 'days') + 1; + } else { + mDuration = moment.utc(m.completionDate).diff(moment.utc(m.startDate), 'days') + 1; } + scheduledDuration += mDuration; + completedDuration += mDuration; + } else { + scheduledDuration += m.duration; } - return Promise.resolve({ duration, progress }); }); - }, - }, - }); + if (scheduledDuration > 0) { + progress = Math.round((completedDuration / scheduledDuration) * 100); + } + } + return Promise.resolve({ duration, progress }); + }); + }; return Milestone; }; diff --git a/src/models/phaseProduct.js b/src/models/phaseProduct.js index 04f97479..d102b0af 100644 --- a/src/models/phaseProduct.js +++ b/src/models/phaseProduct.js @@ -26,37 +26,35 @@ module.exports = function definePhaseProduct(sequelize, DataTypes) { createdAt: 'createdAt', deletedAt: 'deletedAt', indexes: [], - classMethods: { - getActivePhaseProducts(phaseId) { - return this.findAll({ - where: { - deletedAt: { $eq: null }, - phaseId, - }, - raw: true, - }); - }, - /** - * Search Phase Products - * @param {Object} parameters the replacements for sequelize - * - projectId id of the project - * - phaseId id of phase - * @param {Object} log the request log - * @return {Object} the result rows and count - */ - async search(parameters = {}, log) { - const whereQuery = 'phase_products."projectId"= :projectId AND phase_products."phaseId" = :phaseId'; - const dbQuery = `SELECT * FROM phase_products WHERE ${whereQuery}`; - return sequelize.query(dbQuery, - { type: sequelize.QueryTypes.SELECT, - replacements: parameters, - logging: (str) => { log.debug(str); }, - raw: true, - }) - .then(phases => ({ rows: phases, count: phases.length })); - }, + }); + + PhaseProduct.getActivePhaseProducts = phaseId => PhaseProduct.findAll({ + where: { + deletedAt: { $eq: null }, + phaseId, }, + raw: true, }); + /** + * Search Phase Products + * @param {Object} parameters the replacements for sequelize + * - projectId id of the project + * - phaseId id of phase + * @param {Object} log the request log + * @return {Object} the result rows and count + */ + PhaseProduct.search = async (parameters = {}, log) => { + const whereQuery = 'phase_products."projectId"= :projectId AND phase_products."phaseId" = :phaseId'; + const dbQuery = `SELECT * FROM phase_products WHERE ${whereQuery}`; + return sequelize.query(dbQuery, + { type: sequelize.QueryTypes.SELECT, + replacements: parameters, + logging: (str) => { log.debug(str); }, + raw: true, + }) + .then(phases => ({ rows: phases, count: phases.length })); + }; + return PhaseProduct; }; diff --git a/src/models/project.js b/src/models/project.js index a59dc0c4..b7bd9647 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -60,136 +60,142 @@ module.exports = function defineProject(sequelize, DataTypes) { { fields: ['status'] }, { fields: ['directProjectId'] }, ], - classMethods: { - /** - * Get direct project id - * @param id the id of project - */ - getDirectProjectId(id) { - return this.findById(id, { - attributes: ['directProjectId'], - raw: true, - }) - .then(res => res.directProjectId); - }, - associate: (models) => { - Project.hasMany(models.ProjectMember, { as: 'members', foreignKey: 'projectId' }); - Project.hasMany(models.ProjectAttachment, { as: 'attachments', foreignKey: 'projectId' }); - Project.hasMany(models.ProjectPhase, { as: 'phases', foreignKey: 'projectId' }); - Project.hasMany(models.ProjectMemberInvite, { as: 'memberInvites', foreignKey: 'projectId' }); - }, + }); - /** - * Search keyword in name, description, details.utm.code (To be deprecated) - * @param parameters the parameters - * - filters: the filters contains keyword - * - order: the order - * - limit: the limit - * - offset: the offset - * - attributes: the attributes to get - * @param log the request log - * @return the result rows and count - */ - searchText(parameters, log) { - // special handling for keyword filter - let query = '1=1 '; - const replacements = {}; - if (_.has(parameters.filters, 'id')) { - if (_.isObject(parameters.filters.id)) { - if (parameters.filters.id.$in.length === 0) { - parameters.filters.id.$in.push(-1); - } - query += 'AND projects.id IN(:id) '; - replacements.id = parameters.filters.id.$in; - } else if (_.isString(parameters.filters.id) || _.isNumber(parameters.filters.id)) { - query += 'AND id = :id '; - replacements.id = parameters.filters.id; - } - } - if (_.has(parameters.filters, 'status')) { - const statusFilter = parameters.filters.status; - if (_.isObject(statusFilter)) { - query += 'AND projects.status IN (:status) '; - replacements.status = statusFilter.$in; - } else if (_.isString(statusFilter)) { - query += 'AND projects.status = :status'; - replacements.status = statusFilter; - } - } - if (_.has(parameters.filters, 'type')) { - query += 'AND projects.type = :type '; - replacements.type = parameters.filters.type; - } - if (_.has(parameters.filters, 'keyword')) { - query += 'AND projects."projectFullText" ~ lower(:keyword)'; - replacements.keyword = parameters.filters.keyword; - } + Project.associate = (models) => { + Project.hasMany(models.ProjectMember, { as: 'members', foreignKey: 'projectId' }); + Project.hasMany(models.ProjectAttachment, { as: 'attachments', foreignKey: 'projectId' }); + Project.hasMany(models.ProjectPhase, { as: 'phases', foreignKey: 'projectId' }); + Project.hasMany(models.ProjectMemberInvite, { as: 'memberInvites', foreignKey: 'projectId' }); + }; - let joinQuery = ''; - if (_.has(parameters.filters, 'userId') || _.has(parameters.filters, 'email')) { - query += ` AND (members."userId" = :userId - OR invites."userId" = :userId - OR invites."email" = :email) GROUP BY projects.id`; + /** + * Get direct project id + * @param id the id of project + */ + Project.getDirectProjectId = id => Project.findByPk(id, { + attributes: ['directProjectId'], + raw: true, + }) + .then(res => res.directProjectId); - joinQuery = `LEFT OUTER JOIN project_members AS members ON projects.id = members."projectId" - LEFT OUTER JOIN project_member_invites AS invites ON projects.id = invites."projectId"`; - replacements.userId = parameters.filters.userId; - replacements.email = parameters.filters.email; + /** + * Search keyword in name, description, details.utm.code (To be deprecated) + * @param parameters the parameters + * - filters: the filters contains keyword + * - order: the order + * - limit: the limit + * - offset: the offset + * - attributes: the attributes to get + * @param log the request log + * @return the result rows and count + */ + Project.searchText = (parameters, log) => { + // special handling for keyword filter + let query = '1=1 '; + const replacements = {}; + if (_.has(parameters.filters, 'id')) { + if (_.isArray(parameters.filters.id)) { + if (parameters.filters.id.length === 0) { + parameters.filters.id.push(-1); } + query += 'AND projects.id IN(:id) '; + replacements.id = parameters.filters.id; + } else if (_.isString(parameters.filters.id) || _.isNumber(parameters.filters.id)) { + query += 'AND projects.id = :id '; + replacements.id = parameters.filters.id; + } + } + if (_.has(parameters.filters, 'status')) { + const statusFilter = parameters.filters.status; + if (_.isObject(statusFilter)) { + query += 'AND projects.status IN (:status) '; + replacements.status = statusFilter.$in; + } else if (_.isString(statusFilter)) { + query += 'AND projects.status = :status'; + replacements.status = statusFilter; + } + } + if (_.has(parameters.filters, 'type')) { + query += 'AND projects.type = :type '; + replacements.type = parameters.filters.type; + } + if (_.has(parameters.filters, 'keyword')) { + query += 'AND projects."projectFullText" ~ lower(:keyword)'; + replacements.keyword = parameters.filters.keyword; + } + if (_.has(parameters.filters, 'name')) { + query += 'AND projects.name = :name '; + replacements.name = parameters.filters.name; + } + if (_.has(parameters.filters, 'code')) { + query += 'AND details -> \'utm\' ->> \'code\' = :code '; + replacements.code = parameters.filters.code; + } + + let joinQuery = ''; + if (_.has(parameters.filters, 'userId') || _.has(parameters.filters, 'email')) { + query += ` AND (members."userId" = :userId + OR invites."userId" = :userId + OR invites."email" = :email) GROUP BY projects.id`; - let attributesStr = _.map(parameters.attributes, attr => `projects."${attr}"`); - attributesStr = `${attributesStr.join(',')}`; - const orderStr = `"${parameters.order[0][0]}" ${parameters.order[0][1]}`; + joinQuery = `LEFT OUTER JOIN project_members AS members ON projects.id = members."projectId" + LEFT OUTER JOIN project_member_invites AS invites ON projects.id = invites."projectId"`; - // select count of projects - return sequelize.query(`SELECT COUNT(1) FROM projects AS projects + replacements.userId = parameters.filters.userId; + replacements.email = parameters.filters.email; + } + + let attributesStr = _.map(parameters.attributes, attr => `projects."${attr}"`); + attributesStr = `${attributesStr.join(',')}`; + const orderStr = `"${parameters.order[0][0]}" ${parameters.order[0][1]}`; + + // select count of projects + return sequelize.query(`SELECT COUNT(1) FROM projects AS projects + ${joinQuery} + WHERE ${query}`, + { type: sequelize.QueryTypes.SELECT, + replacements, + logging: (str) => { log.debug(str); }, + raw: true, + }) + .then((fcount) => { + let count = fcount.length; + if (fcount.length === 1) { + count = fcount[0].count; + } + + replacements.limit = parameters.limit; + replacements.offset = parameters.offset; + // select project attributes + return sequelize.query(`SELECT ${attributesStr} FROM projects AS projects ${joinQuery} - WHERE ${query}`, + WHERE ${query} ORDER BY ` + + ` projects.${orderStr} LIMIT :limit OFFSET :offset`, { type: sequelize.QueryTypes.SELECT, replacements, logging: (str) => { log.debug(str); }, raw: true, }) - .then((fcount) => { - let count = fcount.length; - if (fcount.length === 1) { - count = fcount[0].count; - } + .then(projects => ({ rows: projects, count })); + }); + }; - replacements.limit = parameters.limit; - replacements.offset = parameters.offset; - // select project attributes - return sequelize.query(`SELECT ${attributesStr} FROM projects AS projects - ${joinQuery} - WHERE ${query} ORDER BY ` + - ` projects.${orderStr} LIMIT :limit OFFSET :offset`, - { type: sequelize.QueryTypes.SELECT, - replacements, - logging: (str) => { log.debug(str); }, - raw: true, - }) - .then(projects => ({ rows: projects, count })); - }); - }, - findProjectRange(models, startId, endId, fields, raw = true) { - return this.findAll({ - where: { id: { $between: [startId, endId] } }, - attributes: _.get(fields, 'projects', null), - raw, - include: [{ - model: models.ProjectPhase, - as: 'phases', - order: [['startDate', 'asc']], - // where: phasesWhere, - include: [{ - model: models.PhaseProduct, - as: 'products', - }], - }], - }); - }, - }, + Project.findProjectRange = (models, startId, endId, fields, raw = true) => Project.findAll({ + where: { id: { $between: [startId, endId] } }, + attributes: _.get(fields, 'projects', null), + raw, + include: [{ + model: models.ProjectPhase, + as: 'phases', + order: [['startDate', 'asc']], + // where: phasesWhere, + include: [{ + model: models.PhaseProduct, + as: 'products', + }], + }], }); return Project; diff --git a/src/models/projectAttachment.js b/src/models/projectAttachment.js index 53043b32..6e23deb9 100644 --- a/src/models/projectAttachment.js +++ b/src/models/projectAttachment.js @@ -24,43 +24,36 @@ module.exports = function defineProjectAttachment(sequelize, DataTypes) { createdAt: 'createdAt', deletedAt: 'deletedAt', indexes: [], - classMethods: { - getActiveProjectAttachments(projectId) { - return this.findAll({ - where: { - deletedAt: { $eq: null }, - projectId, - }, - raw: true, - }); - }, + }); + + ProjectAttachment.getActiveProjectAttachments = projectId => ProjectAttachment.findAll({ + where: { + deletedAt: { $eq: null }, + projectId, + }, + raw: true, + }); - getAttachmentById(projectId, attachmentId) { - return this.findOne({ - where: { - projectId, - id: attachmentId, - }, - }); - }, + ProjectAttachment.getAttachmentById = (projectId, attachmentId) => ProjectAttachment.findOne({ + where: { + projectId, + id: attachmentId, + }, + }); - getAttachmentsForUser(projectId, userId) { - return this.findAll({ - where: { - projectId, - $or: [{ - createdBy: { $eq: userId }, - }, { - allowedUsers: { - $or: [ - { $contains: [userId] }, - { $eq: null }, - ], - }, - }], - }, - }); - }, + ProjectAttachment.getAttachmentsForUser = (projectId, userId) => ProjectAttachment.findAll({ + where: { + projectId, + $or: [{ + createdBy: { $eq: userId }, + }, { + allowedUsers: { + $or: [ + { $contains: [userId] }, + { $eq: null }, + ], + }, + }], }, }); diff --git a/src/models/projectMember.js b/src/models/projectMember.js index 1003c017..d1ed93fb 100644 --- a/src/models/projectMember.js +++ b/src/models/projectMember.js @@ -32,28 +32,24 @@ module.exports = function defineProjectMember(sequelize, DataTypes) { { fields: ['userId'] }, { fields: ['role'] }, ], - classMethods: { - getProjectIdsForUser(userId) { - return this.findAll({ - where: { - deletedAt: { $eq: null }, - userId, - }, - attributes: ['projectId'], - raw: true, - }) - .then(res => _.without(_.map(res, 'projectId'), null)); - }, - getActiveProjectMembers(projectId) { - return this.findAll({ - where: { - deletedAt: { $eq: null }, - projectId, - }, - raw: true, - }); - }, + }); + + ProjectMember.getProjectIdsForUser = userId => ProjectMember.findAll({ + where: { + deletedAt: { $eq: null }, + userId, + }, + attributes: ['projectId'], + raw: true, + }) + .then(res => _.without(_.map(res, 'projectId'), null)); + + ProjectMember.getActiveProjectMembers = projectId => ProjectMember.findAll({ + where: { + deletedAt: { $eq: null }, + projectId, }, + raw: true, }); return ProjectMember; diff --git a/src/models/projectMemberInvite.js b/src/models/projectMemberInvite.js index ae27d0cb..f45d4b78 100644 --- a/src/models/projectMemberInvite.js +++ b/src/models/projectMemberInvite.js @@ -45,65 +45,64 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) { { fields: ['status'] }, { fields: ['deletedAt'] }, ], - classMethods: { - getPendingInvitesForProject(projectId) { - return this.findAll({ - where: { - projectId, - status: INVITE_STATUS.PENDING, - }, - raw: true, - }); - }, - getPendingAndReguestedInvitesForProject(projectId) { - return this.findAll({ - where: { - projectId, - status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] }, - }, - raw: true, - }); - }, - getPendingInviteByEmailOrUserId(projectId, email, userId) { - const where = { projectId, 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 this.findOne({ - where, - }); - }, - getRequestedInvite(projectId, userId) { - const where = { projectId, status: INVITE_STATUS.REQUESTED }; + }); - if (userId) { - _.assign(where, { userId }); - } - return this.findOne({ - where, - }); - }, - getProjectInvitesForUser(email, userId) { - const where = { status: INVITE_STATUS.PENDING }; + ProjectMemberInvite.getPendingInvitesForProject = projectId => ProjectMemberInvite.findAll({ + where: { + projectId, + status: INVITE_STATUS.PENDING, + }, + raw: true, + }); - 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 this.findAll({ - where, - }).then(res => _.without(_.map(res, 'projectId'), null)); - }, + ProjectMemberInvite.getPendingAndReguestedInvitesForProject = projectId => ProjectMemberInvite.findAll({ + where: { + projectId, + status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] }, }, + raw: true, }); + ProjectMemberInvite.getPendingInviteByEmailOrUserId = (projectId, email, userId) => { + const where = { projectId, 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, + }); + }; + + ProjectMemberInvite.getRequestedInvite = (projectId, userId) => { + const where = { projectId, status: INVITE_STATUS.REQUESTED }; + + if (userId) { + _.assign(where, { userId }); + } + return ProjectMemberInvite.findOne({ + where, + }); + }; + + ProjectMemberInvite.getProjectInvitesForUser = (email, userId) => { + const where = { 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.findAll({ + where, + }).then(res => _.without(_.map(res, 'projectId'), null)); + }; + return ProjectMemberInvite; }; diff --git a/src/models/projectPhase.js b/src/models/projectPhase.js index 651c9c70..a74675cf 100644 --- a/src/models/projectPhase.js +++ b/src/models/projectPhase.js @@ -28,49 +28,48 @@ module.exports = function defineProjectPhase(sequelize, DataTypes) { createdAt: 'createdAt', deletedAt: 'deletedAt', indexes: [], - classMethods: { - getActiveProjectPhases(projectId) { - return this.findAll({ - where: { - deletedAt: { $eq: null }, - projectId, - }, - raw: true, - }); - }, - associate: (models) => { - ProjectPhase.hasMany(models.PhaseProduct, { as: 'products', foreignKey: 'phaseId' }); - }, - /** - * Search project phases - * @param {Object} parameters the parameters - * - sortField: the field that will be references when sorting - * - sortType: ASC or DESC - * - fields: the fields to retrieved - * - projectId: the id of project - * @param {Object} log the request log - * @return {Object} the result rows and count - */ - async search(parameters = {}, log) { - let fieldsStr = _.map(parameters.fields, field => `project_phases."${field}"`); - fieldsStr = `${fieldsStr.join(',')}`; - const replacements = { - projectId: parameters.projectId, - }; - let dbQuery = `SELECT ${fieldsStr} FROM project_phases WHERE project_phases."projectId" = :projectId`; - if (_.has(parameters, 'sortField') && _.has(parameters, 'sortType')) { - dbQuery = `${dbQuery} ORDER BY project_phases."${parameters.sortField}" ${parameters.sortType}`; - } - return sequelize.query(dbQuery, - { type: sequelize.QueryTypes.SELECT, - logging: (str) => { log.debug(str); }, - replacements, - raw: true, - }) - .then(phases => ({ rows: phases, count: phases.length })); - }, + }); + + ProjectPhase.getActiveProjectPhases = projectId => ProjectPhase.findAll({ + where: { + deletedAt: { $eq: null }, + projectId, }, + raw: true, }); + ProjectPhase.associate = (models) => { + ProjectPhase.hasMany(models.PhaseProduct, { as: 'products', foreignKey: 'phaseId' }); + }; + + /** + * Search project phases + * @param {Object} parameters the parameters + * - sortField: the field that will be references when sorting + * - sortType: ASC or DESC + * - fields: the fields to retrieved + * - projectId: the id of project + * @param {Object} log the request log + * @return {Object} the result rows and count + */ + ProjectPhase.search = async (parameters = {}, log) => { + let fieldsStr = _.map(parameters.fields, field => `project_phases."${field}"`); + fieldsStr = `${fieldsStr.join(',')}`; + const replacements = { + projectId: parameters.projectId, + }; + let dbQuery = `SELECT ${fieldsStr} FROM project_phases WHERE project_phases."projectId" = :projectId`; + if (_.has(parameters, 'sortField') && _.has(parameters, 'sortType')) { + dbQuery = `${dbQuery} ORDER BY project_phases."${parameters.sortField}" ${parameters.sortType}`; + } + return sequelize.query(dbQuery, + { type: sequelize.QueryTypes.SELECT, + logging: (str) => { log.debug(str); }, + replacements, + raw: true, + }) + .then(phases => ({ rows: phases, count: phases.length })); + }; + return ProjectPhase; }; diff --git a/src/models/timeline.js b/src/models/timeline.js index 5b9d6247..8ce0996a 100644 --- a/src/models/timeline.js +++ b/src/models/timeline.js @@ -25,12 +25,11 @@ module.exports = (sequelize, DataTypes) => { updatedAt: 'updatedAt', createdAt: 'createdAt', deletedAt: 'deletedAt', - classMethods: { - associate: (models) => { - Timeline.hasMany(models.Milestone, { as: 'milestones', foreignKey: 'timelineId', onDelete: 'cascade' }); - }, - }, }); + Timeline.associate = (models) => { + Timeline.hasMany(models.Milestone, { as: 'milestones', foreignKey: 'timelineId', onDelete: 'cascade' }); + }; + return Timeline; }; diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index 23a3ad9b..4c3bcaf9 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -25,7 +25,7 @@ const addAttachmentValidations = { filePath: Joi.string().required(), s3Bucket: Joi.string().required(), contentType: Joi.string().required(), - allowedUsers: Joi.array(Joi.number().integer().positive()).allow(null).default(null), + allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null), }).required(), }, }; diff --git a/src/routes/attachments/create.spec.js b/src/routes/attachments/create.spec.js index 5cbae3fb..ef0d6650 100644 --- a/src/routes/attachments/create.spec.js +++ b/src/routes/attachments/create.spec.js @@ -103,7 +103,7 @@ describe('Project Attachments', () => { describe('POST /projects/{id}/attachments/', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .post(`/v4/projects/${project1.id}/attachments/`) + .post(`/v5/projects/${project1.id}/attachments/`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -114,7 +114,7 @@ describe('Project Attachments', () => { it('should return 201 return attachment record', (done) => { request(server) - .post(`/v4/projects/${project1.id}/attachments/`) + .post(`/v5/projects/${project1.id}/attachments/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -152,7 +152,7 @@ describe('Project Attachments', () => { it('sends single BUS_API_EVENT.PROJECT_FILES_UPDATED message when attachment added', (done) => { request(server) - .post(`/v4/projects/${project1.id}/attachments/`) + .post(`/v5/projects/${project1.id}/attachments/`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/attachments/delete.spec.js b/src/routes/attachments/delete.spec.js index bf73d585..6f910152 100644 --- a/src/routes/attachments/delete.spec.js +++ b/src/routes/attachments/delete.spec.js @@ -73,7 +73,7 @@ describe('Project Attachments delete', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -83,7 +83,7 @@ describe('Project Attachments delete', () => { it('should return 404 if attachment was not found', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/attachments/8888888`) + .delete(`/v5/projects/${project1.id}/attachments/8888888`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -109,7 +109,7 @@ describe('Project Attachments delete', () => { const deleteSpy = sinon.spy(mockHttpClient, 'delete'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .delete(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -136,7 +136,7 @@ describe('Project Attachments delete', () => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -149,7 +149,7 @@ describe('Project Attachments delete', () => { it('should return 204 if ADMIN deletes the attachment successfully', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -160,7 +160,7 @@ describe('Project Attachments delete', () => { done(err); } else { request(server) - .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -183,7 +183,7 @@ describe('Project Attachments delete', () => { it('sends single BUS_API_EVENT.PROJECT_FILES_UPDATED message when attachment deleted', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/attachments/download.spec.js b/src/routes/attachments/download.spec.js index 8cfc80cb..84ce6821 100644 --- a/src/routes/attachments/download.spec.js +++ b/src/routes/attachments/download.spec.js @@ -76,7 +76,7 @@ describe('Project Attachments download', () => { it('should return 403 if USER does not have permissions', (done) => { request(server) - .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -86,7 +86,7 @@ describe('Project Attachments download', () => { it('should return 403 if MANAGER does not have permissions', (done) => { request(server) - .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -96,7 +96,7 @@ describe('Project Attachments download', () => { it('should return 404 if attachment was not found', (done) => { request(server) - .get(`/v4/projects/${project1.id}/attachments/8888888`) + .get(`/v5/projects/${project1.id}/attachments/8888888`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -106,7 +106,7 @@ describe('Project Attachments download', () => { it('should return 200 when the CREATOR can download attachment successfully', (done) => { request(server) - .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -116,7 +116,7 @@ describe('Project Attachments download', () => { it('should return 200 when the USER with permission can download attachment successfully', (done) => { request(server) - .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -126,7 +126,7 @@ describe('Project Attachments download', () => { it('should return 200 when ADMIN can download attachment successfully', (done) => { request(server) - .get(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .get(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/attachments/update.js b/src/routes/attachments/update.js index 0bf90738..c276f644 100644 --- a/src/routes/attachments/update.js +++ b/src/routes/attachments/update.js @@ -19,7 +19,7 @@ const updateProjectAttachmentValidation = { param: Joi.object().keys({ title: Joi.string().required(), description: Joi.string().optional().allow(null).allow(''), - allowedUsers: Joi.array(Joi.number().integer().positive()).allow(null).default(null), + allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null), }), }, }; diff --git a/src/routes/attachments/update.spec.js b/src/routes/attachments/update.spec.js index 0796e22f..44982c59 100644 --- a/src/routes/attachments/update.spec.js +++ b/src/routes/attachments/update.spec.js @@ -73,7 +73,7 @@ describe('Project Attachments update', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -83,7 +83,7 @@ describe('Project Attachments update', () => { it('should return 404 if attachment was not found', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/attachments/8888888`) + .patch(`/v5/projects/${project1.id}/attachments/8888888`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -93,7 +93,7 @@ describe('Project Attachments update', () => { it('should return 200 if attachment was successfully updated', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -114,7 +114,7 @@ describe('Project Attachments update', () => { it('should return 200 if admin updates the attachment', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -147,7 +147,7 @@ describe('Project Attachments update', () => { it('sends single BUS_API_EVENT.PROJECT_FILES_UPDATED message when attachment updated', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/attachments/${attachment.id}`) + .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/form/revision/create.js b/src/routes/form/revision/create.js index c1f570a7..99c814f2 100644 --- a/src/routes/form/revision/create.js +++ b/src/routes/form/revision/create.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; import util from '../../../util'; import models from '../../../models'; @@ -15,18 +16,16 @@ const schema = { key: Joi.string().max(45).required(), version: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - config: Joi.object().required(), + body: Joi.object().keys({ + config: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -43,13 +42,13 @@ module.exports = [ if (form) { const version = form ? form.version : 1; const revision = form ? form.revision + 1 : 1; - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { version, revision, createdBy: req.authUser.userId, updatedBy: req.authUser.userId, key: req.params.key, - config: req.body.param.config, + config: req.body.config, }); return models.Form.create(entity); } @@ -57,9 +56,12 @@ module.exports = [ apiErr.status = 404; return Promise.reject(apiErr); }).then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.FORM_REVISION, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/form/revision/create.spec.js b/src/routes/form/revision/create.spec.js index d23deaf7..3cfcf3c3 100644 --- a/src/routes/form/revision/create.spec.js +++ b/src/routes/form/revision/create.spec.js @@ -35,32 +35,32 @@ describe('CREATE Form Revision', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('Post /projects/metadata/form/{key}/versions/{version}/revision', () => { const body = { - param: { - config: { - 'test create': 'test create', - }, + config: { + 'test create': 'test create', }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/form/dev/versions/1/revisions') + .post('/v5/projects/metadata/form/dev/versions/1/revisions') .send(body) .expect(403, done); }); it('should return 404 if missing key', (done) => { request(server) - .post('/v4/projects/metadata/form/no-exist-key/versions/1/revisions') + .post('/v5/projects/metadata/form/no-exist-key/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -71,7 +71,7 @@ describe('CREATE Form Revision', () => { it('should return 404 if missing version', (done) => { request(server) - .post('/v4/projects/metadata/form/dev/versions/100/revisions') + .post('/v5/projects/metadata/form/dev/versions/100/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -80,26 +80,24 @@ describe('CREATE Form Revision', () => { .expect(404, done); }); - it('should return 422 if missing config', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - config: undefined, - }), - }; + it('should return 400 if missing config', (done) => { + const invalidBody = _.assign({}, body, { + config: undefined, + }); request(server) - .post('/v4/projects/metadata/form/no-exist-key/versions/1/revisions') + .post('/v5/projects/metadata/form/no-exist-key/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/form/dev/versions/1/revisions') + .post('/v5/projects/metadata/form/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -107,9 +105,9 @@ describe('CREATE Form Revision', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.config.should.be.eql(body.param.config); + resJson.config.should.be.eql(body.config); resJson.key.should.be.eql('dev'); resJson.revision.should.be.eql(3); resJson.version.should.be.eql(1); @@ -125,7 +123,7 @@ describe('CREATE Form Revision', () => { it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/form/dev/versions/1/revisions') + .post('/v5/projects/metadata/form/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/form/revision/delete.js b/src/routes/form/revision/delete.js index 1abaf2ec..e06b9f80 100644 --- a/src/routes/form/revision/delete.js +++ b/src/routes/form/revision/delete.js @@ -2,8 +2,11 @@ * API to delete a revsion */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; +import util from '../../../util'; import models from '../../../models'; const permissions = tcMiddleware.permissions; @@ -38,9 +41,12 @@ module.exports = [ return form.update({ deletedBy: req.authUser.userId, }); - }).then((form) => { - form.destroy(); - }).then(() => { + }).then(form => form.destroy()) + .then((form) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.FORM_REVISION, + _.pick(form.toJSON(), 'id')); res.status(204).end(); }) .catch(next)); diff --git a/src/routes/form/revision/delete.spec.js b/src/routes/form/revision/delete.spec.js index 6bcd95c6..8aee319f 100644 --- a/src/routes/form/revision/delete.spec.js +++ b/src/routes/form/revision/delete.spec.js @@ -26,7 +26,7 @@ const expectAfterDelete = (err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions/1') + .get('/v5/projects/metadata/form/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -60,24 +60,25 @@ describe('DELETE form revision', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); - + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/form/{key}/versions/{version}/revisions/{revision}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1') .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -86,7 +87,7 @@ describe('DELETE form revision', () => { it('should return 403 for copilot', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -95,7 +96,7 @@ describe('DELETE form revision', () => { it('should return 403 for manager', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -104,7 +105,7 @@ describe('DELETE form revision', () => { it('should return 404 for non-existed key', (done) => { request(server) - .delete('/v4/projects/metadata/form/no-existed-key/versions/1/revisions/1') + .delete('/v5/projects/metadata/form/no-existed-key/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -113,7 +114,7 @@ describe('DELETE form revision', () => { it('should return 404 for non-existed version', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/100/revisions/1') + .delete('/v5/projects/metadata/form/dev/versions/100/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -123,7 +124,7 @@ describe('DELETE form revision', () => { it('should return 404 for non-existed revision', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1/revisions/100') + .delete('/v5/projects/metadata/form/dev/versions/1/revisions/100') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -132,7 +133,7 @@ describe('DELETE form revision', () => { it('should return 204, for admin', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -142,7 +143,7 @@ describe('DELETE form revision', () => { it('should return 204, for connect admin', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/form/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/form/revision/get.js b/src/routes/form/revision/get.js index 460331cf..4fdb08a7 100644 --- a/src/routes/form/revision/get.js +++ b/src/routes/form/revision/get.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -20,25 +20,58 @@ const schema = { module.exports = [ validate(schema), permissions('form.view'), - (req, res, next) => models.Form.findOne({ - where: { - key: req.params.key, - version: req.params.version, - revision: req.params.revision, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((form) => { - // Not found - if (!form) { - const apiErr = new Error('Form not found for key' + - ` ${req.params.key} version ${req.params.version} revision ${req.params.revision}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => { + util.fetchByIdFromES('forms', { + query: { + nested: { + path: 'forms', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'forms.key': req.params.key } }, + { term: { 'forms.version': req.params.version } }, + { term: { 'forms.revision': req.params.revision } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No form found in ES'); + models.Form.findOne({ + where: { + key: req.params.key, + version: req.params.version, + revision: req.params.revision, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((form) => { + // Not found + if (!form) { + const apiErr = new Error('Form not found for key' + + ` ${req.params.key} version ${req.params.version} revision ${req.params.revision}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, form)); - return Promise.resolve(); + res.json(form); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('forms found in ES'); + res.json(data[0].inner_hits.forms.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } }) - .catch(next), + .catch(next); + }, ]; diff --git a/src/routes/form/revision/get.spec.js b/src/routes/form/revision/get.spec.js index 9e2b218f..bdcdff23 100644 --- a/src/routes/form/revision/get.spec.js +++ b/src/routes/form/revision/get.spec.js @@ -34,24 +34,26 @@ describe('GET a particular revision of specific version Form', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/form/dev/versions/{version}/revisions/{revision}', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/form/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const form = forms[1]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(form.key); resJson.config.should.be.eql(form.config); @@ -68,13 +70,13 @@ describe('GET a particular revision of specific version Form', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/form/dev/versions/1/revisions/2') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/form/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -84,7 +86,7 @@ describe('GET a particular revision of specific version Form', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/form/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -94,7 +96,7 @@ describe('GET a particular revision of specific version Form', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/form/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -103,7 +105,7 @@ describe('GET a particular revision of specific version Form', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/form/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/form/revision/list.js b/src/routes/form/revision/list.js index d4acc1d0..ab0ab9f9 100644 --- a/src/routes/form/revision/list.js +++ b/src/routes/form/revision/list.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -19,23 +19,32 @@ const schema = { module.exports = [ validate(schema), permissions('form.view'), - (req, res, next) => models.Form.findAll({ - where: { - key: req.params.key, - version: req.params.version, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((forms) => { - // Not found - if ((!forms) || (forms.length === 0)) { - const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => + util.fetchFromES('forms') + .then((data) => { + if (data.forms.length === 0) { + req.log.debug('No form found in ES'); + models.Form.findAll({ + where: { + key: req.params.key, + version: req.params.version, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }).then((forms) => { + // Not found + if ((!forms) || (forms.length === 0)) { + const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, forms)); - return Promise.resolve(); - }) - .catch(next), + res.json(forms); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('forms found in ES'); + res.json(data.forms); + } + }).catch(next), ]; diff --git a/src/routes/form/revision/list.spec.js b/src/routes/form/revision/list.spec.js index b2be931d..d239b03b 100644 --- a/src/routes/form/revision/list.spec.js +++ b/src/routes/form/revision/list.spec.js @@ -35,24 +35,26 @@ describe('LIST form revisions', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/form/dev/versions/{version}/revisions', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions') + .get('/v5/projects/metadata/form/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const form = forms[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].key.should.be.eql(form.key); @@ -70,13 +72,13 @@ describe('LIST form revisions', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions') + .get('/v5/projects/metadata/form/dev/versions/1/revisions') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions') + .get('/v5/projects/metadata/form/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -86,7 +88,7 @@ describe('LIST form revisions', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions') + .get('/v5/projects/metadata/form/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -96,7 +98,7 @@ describe('LIST form revisions', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions') + .get('/v5/projects/metadata/form/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -105,7 +107,7 @@ describe('LIST form revisions', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1/revisions') + .get('/v5/projects/metadata/form/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/form/version/create.js b/src/routes/form/version/create.js index 2a16d113..00a16119 100644 --- a/src/routes/form/version/create.js +++ b/src/routes/form/version/create.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; import util from '../../../util'; import models from '../../../models'; @@ -14,18 +15,18 @@ const schema = { params: { key: Joi.string().max(45).required(), }, - body: { - param: Joi.object().keys({ - config: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + config: Joi.object().required(), + + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), + }; module.exports = [ @@ -45,19 +46,22 @@ module.exports = [ latestVersion = latestVersionForm.version + 1; } - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { version: latestVersion, revision: 1, createdBy: req.authUser.userId, updatedBy: req.authUser.userId, key: req.params.key, - config: req.body.param.config, + config: req.body.config, }); return models.Form.create(entity); }).then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.FORM_VERSION, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/form/version/create.spec.js b/src/routes/form/version/create.spec.js index 68a27ba5..f94eb023 100644 --- a/src/routes/form/version/create.spec.js +++ b/src/routes/form/version/create.spec.js @@ -35,49 +35,56 @@ describe('CREATE Form version', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); + + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('Post /projects/metadata/form/{key}/versions/', () => { const body = { - param: { - config: { - 'test create': 'test create', - }, + config: { + 'test create': 'test create', }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/form/dev/versions') + .post('/v5/projects/metadata/form/dev/versions') .send(body) .expect(403, done); }); - it('should return 422 if missing config', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - config: undefined, - }), - }; + it('should return 400 if missing config', (done) => { + const invalidBody = _.assign({}, body, { + config: undefined, + }); request(server) - .post('/v4/projects/metadata/form/dev/versions/') + .post('/v5/projects/metadata/form/dev/versions/') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/form/dev/versions/') + .post('/v5/projects/metadata/form/dev/versions/') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -85,9 +92,9 @@ describe('CREATE Form version', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.config.should.be.eql(body.param.config); + resJson.config.should.be.eql(body.config); resJson.key.should.be.eql('dev'); resJson.revision.should.be.eql(1); resJson.version.should.be.eql(2); @@ -103,7 +110,7 @@ describe('CREATE Form version', () => { it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/form/dev/versions/') + .post('/v5/projects/metadata/form/dev/versions/') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/form/version/delete.js b/src/routes/form/version/delete.js index cd195934..05359a37 100644 --- a/src/routes/form/version/delete.js +++ b/src/routes/form/version/delete.js @@ -2,8 +2,11 @@ * API to add a project type */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; +import util from '../../../util'; import models from '../../../models'; const permissions = tcMiddleware.permissions; @@ -46,7 +49,20 @@ module.exports = [ key: req.params.key, version: req.params.version, }, - })).then(() => { + })).then(deleted => models.Form.findAll({ + where: { + key: req.params.key, + version: req.params.version, + }, + paranoid: false, + order: [['deletedAt', 'DESC']], + limit: deleted, + })) + .then((forms) => { + _.map(forms, form => util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.FORM_VERSION, + _.pick(form.toJSON(), 'id'))); res.status(204).end(); }) .catch(next)); diff --git a/src/routes/form/version/delete.spec.js b/src/routes/form/version/delete.spec.js index 0f3fd95e..8aff6a9b 100644 --- a/src/routes/form/version/delete.spec.js +++ b/src/routes/form/version/delete.spec.js @@ -56,24 +56,26 @@ describe('DELETE form version', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/form/{key}/versions/{version}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1') + .delete('/v5/projects/metadata/form/dev/versions/1') .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1') + .delete('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -82,7 +84,7 @@ describe('DELETE form version', () => { it('should return 403 for copilot', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1') + .delete('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -91,7 +93,7 @@ describe('DELETE form version', () => { it('should return 403 for manager', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1') + .delete('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -100,7 +102,7 @@ describe('DELETE form version', () => { it('should return 404 for non-existed key', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev111/versions/1') + .delete('/v5/projects/metadata/form/dev111/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -109,7 +111,7 @@ describe('DELETE form version', () => { it('should return 404 for non-existed version', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/111') + .delete('/v5/projects/metadata/form/dev/versions/111') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -118,7 +120,7 @@ describe('DELETE form version', () => { it('should return 204, for admin', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1') + .delete('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -128,7 +130,7 @@ describe('DELETE form version', () => { it('should return 204, for connect admin', (done) => { request(server) - .delete('/v4/projects/metadata/form/dev/versions/1') + .delete('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/form/version/get.js b/src/routes/form/version/get.js index e68c6ac3..a2850043 100644 --- a/src/routes/form/version/get.js +++ b/src/routes/form/version/get.js @@ -5,8 +5,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -19,16 +19,47 @@ const schema = { module.exports = [ validate(schema), permissions('form.view'), - (req, res, next) => - models.Form.latestRevisionOfLatestVersion(req.params.key) - .then((form) => { - if (form == null) { - const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } - res.json(util.wrapResponse(req.id, form)); - return Promise.resolve(); - }) - .catch(next), + (req, res, next) => { + util.fetchByIdFromES('forms', { + query: { + nested: { + path: 'forms', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'forms.key': req.params.key } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + sort: { 'forms.version': 'desc' }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No form found in ES'); + models.Form.latestRevisionOfLatestVersion(req.params.key) + .then((form) => { + if (form == null) { + const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + res.json(form); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('forms found in ES'); + res.json(data[0].inner_hits.forms.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } + }) + .catch(next); + }, ]; diff --git a/src/routes/form/version/get.spec.js b/src/routes/form/version/get.spec.js index 64abd727..326d4ef7 100644 --- a/src/routes/form/version/get.spec.js +++ b/src/routes/form/version/get.spec.js @@ -54,25 +54,27 @@ describe('GET a latest version of specific key of Form', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => models.Form.create(forms[2])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1])) + .then(() => models.Form.create(forms[2]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/form/dev/versions/{version}', () => { it('should return 200 and correct version for admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev') + .get('/v5/projects/metadata/form/dev') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const form = forms[2]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(form.key); resJson.config.should.be.eql(form.config); resJson.version.should.be.eql(form.version); @@ -88,13 +90,13 @@ describe('GET a latest version of specific key of Form', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/form/dev') + .get('/v5/projects/metadata/form/dev') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev') + .get('/v5/projects/metadata/form/dev') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -104,7 +106,7 @@ describe('GET a latest version of specific key of Form', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/form/dev') + .get('/v5/projects/metadata/form/dev') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -114,7 +116,7 @@ describe('GET a latest version of specific key of Form', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/form/dev') + .get('/v5/projects/metadata/form/dev') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -123,7 +125,7 @@ describe('GET a latest version of specific key of Form', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/form/dev') + .get('/v5/projects/metadata/form/dev') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/form/version/getVersion.js b/src/routes/form/version/getVersion.js index 74480b60..1c5fb463 100644 --- a/src/routes/form/version/getVersion.js +++ b/src/routes/form/version/getVersion.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -19,24 +19,56 @@ const schema = { module.exports = [ validate(schema), permissions('form.view'), - (req, res, next) => models.Form.findOne({ - where: { - key: req.params.key, - version: req.params.version, - }, - order: [['revision', 'DESC']], - limit: 1, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((form) => { - // Not found - if (!form) { - const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`); - apiErr.status = 404; - return Promise.reject(apiErr); + (req, res, next) => { + util.fetchByIdFromES('forms', { + query: { + nested: { + path: 'forms', + query: { + filtered: { + filter: { + bool: { + must: [ + { term: { 'forms.key': req.params.key } }, + { term: { 'forms.version': req.params.version } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + sort: { 'forms.revision': 'desc' }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No form found in ES'); + models.Form.findOne({ + where: { + key: req.params.key, + version: req.params.version, + }, + order: [['revision', 'DESC']], + limit: 1, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((form) => { + // Not found + if (!form) { + const apiErr = new Error(`Form not found for key ${req.params.key} version ${req.params.version}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + res.json(form); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('forms found in ES'); + res.json(data[0].inner_hits.forms.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle } - res.json(util.wrapResponse(req.id, form)); - return Promise.resolve(); }) - .catch(next), + .catch(next); + }, ]; diff --git a/src/routes/form/version/getVersion.spec.js b/src/routes/form/version/getVersion.spec.js index 60b3f2ec..14ac9096 100644 --- a/src/routes/form/version/getVersion.spec.js +++ b/src/routes/form/version/getVersion.spec.js @@ -44,25 +44,27 @@ describe('GET a particular version of specific key of Form', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => models.Form.create(forms[2])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1])) + .then(() => models.Form.create(forms[2]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/form/dev/versions/{version}', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1') + .get('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const form = forms[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(form.key); resJson.config.should.be.eql(form.config); @@ -79,13 +81,13 @@ describe('GET a particular version of specific key of Form', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1') + .get('/v5/projects/metadata/form/dev/versions/1') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev') + .get('/v5/projects/metadata/form/dev') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -95,7 +97,7 @@ describe('GET a particular version of specific key of Form', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1') + .get('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -105,7 +107,7 @@ describe('GET a particular version of specific key of Form', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1') + .get('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -114,7 +116,7 @@ describe('GET a particular version of specific key of Form', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions/1') + .get('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/form/version/list.js b/src/routes/form/version/list.js index 1186c55c..383cec6f 100644 --- a/src/routes/form/version/list.js +++ b/src/routes/form/version/list.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -18,30 +18,40 @@ const schema = { module.exports = [ validate(schema), permissions('form.view'), - (req, res, next) => models.Form.findAll({ - where: { - key: req.params.key, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((forms) => { - // Not found - if ((!forms) || (forms.length === 0)) { - const apiErr = new Error(`Form not found for key ${req.params.key}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => + util.fetchFromES('forms') + .then((data) => { + if (data.forms.length === 0) { + req.log.debug('No form found in ES'); + models.Form.findAll({ + where: { + key: req.params.key, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((forms) => { + // Not found + if ((!forms) || (forms.length === 0)) { + const apiErr = new Error(`Form not found for key ${req.params.key}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - const latestForms = {}; - forms.forEach((element) => { - const isNewerRevision = (latestForms[element.version] != null) && - (latestForms[element.version].revision < element.revision); - if ((latestForms[element.version] == null) || isNewerRevision) { - latestForms[element.version] = element; - } - }); - res.json(util.wrapResponse(req.id, Object.values(latestForms))); - return Promise.resolve(); - }) - .catch(next), + const latestForms = {}; + forms.forEach((element) => { + const isNewerRevision = (latestForms[element.version] != null) && + (latestForms[element.version].revision < element.revision); + if ((latestForms[element.version] == null) || isNewerRevision) { + latestForms[element.version] = element; + } + }); + res.json(Object.values(latestForms)); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('forms found in ES'); + res.json(data.forms); + } + }).catch(next), ]; diff --git a/src/routes/form/version/list.spec.js b/src/routes/form/version/list.spec.js index 019a0a72..14977b5e 100644 --- a/src/routes/form/version/list.spec.js +++ b/src/routes/form/version/list.spec.js @@ -35,24 +35,26 @@ describe('LIST form versions', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/form/dev/versions/{version}', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions') + .get('/v5/projects/metadata/form/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const form = forms[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].key.should.be.eql(form.key); @@ -70,13 +72,13 @@ describe('LIST form versions', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions') + .get('/v5/projects/metadata/form/dev/versions') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions') + .get('/v5/projects/metadata/form/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -86,7 +88,7 @@ describe('LIST form versions', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions') + .get('/v5/projects/metadata/form/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -96,7 +98,7 @@ describe('LIST form versions', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions') + .get('/v5/projects/metadata/form/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -105,7 +107,7 @@ describe('LIST form versions', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/form/dev/versions') + .get('/v5/projects/metadata/form/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/form/version/update.js b/src/routes/form/version/update.js index ebb7294d..b484f830 100644 --- a/src/routes/form/version/update.js +++ b/src/routes/form/version/update.js @@ -7,6 +7,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; import util from '../../../util'; import models from '../../../models'; @@ -17,18 +18,18 @@ const schema = { version: Joi.number().integer().positive().required(), key: Joi.string().max(45).required(), }, - body: { - param: Joi.object().keys({ - config: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + config: Joi.object().required(), + + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), + }; module.exports = [ @@ -60,14 +61,17 @@ module.exports = [ createdBy: req.authUser.userId, updatedBy: req.authUser.userId, key: req.params.key, - config: req.body.param.config, + config: req.body.config, }; return models.Form.create(entity); }) .then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.FORM_VERSION, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/form/version/update.spec.js b/src/routes/form/version/update.spec.js index 4f66e975..0bce1fd7 100644 --- a/src/routes/form/version/update.spec.js +++ b/src/routes/form/version/update.spec.js @@ -35,48 +35,46 @@ describe('UPDATE Form version', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('Post /projects/metadata/form/{key}/versions/{version}', () => { const body = { - param: { - config: { - 'test create': 'test create', - }, + config: { + 'test create': 'test create', }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .patch('/v4/projects/metadata/form/dev/versions/1') + .patch('/v5/projects/metadata/form/dev/versions/1') .send(body) .expect(403, done); }); - it('should return 422 if missing config', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - config: undefined, - }), - }; + it('should return 400 if missing config', (done) => { + const invalidBody = _.assign({}, body, { + config: undefined, + }); request(server) - .patch('/v4/projects/metadata/form/dev/versions/1') + .patch('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .patch('/v4/projects/metadata/form/dev/versions/1') + .patch('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -84,9 +82,9 @@ describe('UPDATE Form version', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.config.should.be.eql(body.param.config); + resJson.config.should.be.eql(body.config); resJson.key.should.be.eql('dev'); resJson.revision.should.be.eql(3); resJson.version.should.be.eql(1); @@ -102,7 +100,7 @@ describe('UPDATE Form version', () => { it('should return 403 for member', (done) => { request(server) - .patch('/v4/projects/metadata/form/dev/versions/1') + .patch('/v5/projects/metadata/form/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/index.js b/src/routes/index.js index c6f711ef..09dca3cc 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -10,7 +10,7 @@ const router = Router(); const apiVersion = config.apiVersion; validate.options({ - status: 422, + status: 400, flatten: true, allowUnknownBody: false, }); @@ -34,239 +34,239 @@ router.all( ), ); -router.route('/v4/projects/metadata/projectTemplates') +router.route('/v5/projects/metadata/projectTemplates') .get(require('./projectTemplates/list')); -router.route('/v4/projects/metadata/projectTemplates/:templateId(\\d+)') +router.route('/v5/projects/metadata/projectTemplates/:templateId(\\d+)') .get(require('./projectTemplates/get')); -router.route('/v4/projects/metadata/productTemplates') +router.route('/v5/projects/metadata/productTemplates') .get(require('./productTemplates/list')); -router.route('/v4/projects/metadata/productTemplates/:templateId(\\d+)') +router.route('/v5/projects/metadata/productTemplates/:templateId(\\d+)') .get(require('./productTemplates/get')); -router.route('/v4/projects/metadata/productTemplates/:templateId(\\d+)/upgrade') +router.route('/v5/projects/metadata/productTemplates/:templateId(\\d+)/upgrade') .post(require('./productTemplates/upgrade')); -router.route('/v4/projects/metadata/projectTypes') +router.route('/v5/projects/metadata/projectTypes') .get(require('./projectTypes/list')); -router.route('/v4/projects/metadata/projectTypes/:key') +router.route('/v5/projects/metadata/projectTypes/:key') .get(require('./projectTypes/get')); -router.route('/v4/projects/metadata/projectTemplates/:templateId(\\d+)/upgrade') +router.route('/v5/projects/metadata/projectTemplates/:templateId(\\d+)/upgrade') .post(require('./projectTemplates/upgrade')); -router.route('/v4/projects/metadata/orgConfig') +router.route('/v5/projects/metadata/orgConfig') .get(require('./orgConfig/list')); -router.route('/v4/projects/metadata/orgConfig/:id(\\d+)') +router.route('/v5/projects/metadata/orgConfig/:id(\\d+)') .get(require('./orgConfig/get')); -router.route('/v4/projects/metadata/productCategories') +router.route('/v5/projects/metadata/productCategories') .get(require('./productCategories/list')); -router.route('/v4/projects/metadata/productCategories/:key') +router.route('/v5/projects/metadata/productCategories/:key') .get(require('./productCategories/get')); -router.use('/v4/projects/metadata', compression()); -router.route('/v4/projects/metadata') +router.use('/v5/projects/metadata', compression()); +router.route('/v5/projects/metadata') .get(require('./metadata/list')); // Register all the routes -router.use('/v4/projects', compression()); -router.route('/v4/projects') +router.use('/v5/projects', compression()); +router.route('/v5/projects') .post(require('./projects/create')) .get(require('./projects/list')); -router.use('/v4/projects/db', compression()); -router.route('/v4/projects/db') +router.use('/v5/projects/db', compression()); +router.route('/v5/projects/db') .get(require('./projects/list-db')); -router.route('/v4/projects/admin/es/project/createIndex') +router.route('/v5/projects/admin/es/project/createIndex') .post(require('./admin/project-create-index')); -router.route('/v4/projects/admin/es/project/deleteIndex') +router.route('/v5/projects/admin/es/project/deleteIndex') .delete(require('./admin/project-delete-index')); -router.route('/v4/projects/admin/es/project/index') +router.route('/v5/projects/admin/es/project/index') .post(require('./admin/project-index-create')); -router.route('/v4/projects/admin/es/project/remove') +router.route('/v5/projects/admin/es/project/remove') .delete(require('./admin/project-index-delete')); -router.route('/v4/projects/:projectId(\\d+)') +router.route('/v5/projects/:projectId(\\d+)') .get(require('./projects/get')) .patch(require('./projects/update')) .delete(require('./projects/delete')); -router.route('/v4/projects/:projectId(\\d+)/members') +router.route('/v5/projects/:projectId(\\d+)/members') .post(require('./projectMembers/create')); -router.route('/v4/projects/:projectId(\\d+)/members/:id(\\d+)') +router.route('/v5/projects/:projectId(\\d+)/members/:id(\\d+)') .delete(require('./projectMembers/delete')) .patch(require('./projectMembers/update')); -router.route('/v4/projects/:projectId(\\d+)/attachments') +router.route('/v5/projects/:projectId(\\d+)/attachments') .post(require('./attachments/create')); -router.route('/v4/projects/:projectId(\\d+)/attachments/:id(\\d+)') +router.route('/v5/projects/:projectId(\\d+)/attachments/:id(\\d+)') .get(require('./attachments/download')) .patch(require('./attachments/update')) .delete(require('./attachments/delete')); -router.route('/v4/projects/:projectId(\\d+)/upgrade') +router.route('/v5/projects/:projectId(\\d+)/upgrade') .post(require('./projectUpgrade/create')); -router.route('/v4/projects/metadata/projectTemplates') +router.route('/v5/projects/metadata/projectTemplates') .post(require('./projectTemplates/create')); -router.route('/v4/projects/metadata/projectTemplates/:templateId(\\d+)') +router.route('/v5/projects/metadata/projectTemplates/:templateId(\\d+)') .patch(require('./projectTemplates/update')) .delete(require('./projectTemplates/delete')); -router.route('/v4/projects/metadata/productTemplates') +router.route('/v5/projects/metadata/productTemplates') .post(require('./productTemplates/create')); -router.route('/v4/projects/metadata/productTemplates/:templateId(\\d+)') +router.route('/v5/projects/metadata/productTemplates/:templateId(\\d+)') .patch(require('./productTemplates/update')) .delete(require('./productTemplates/delete')); -router.route('/v4/projects/:projectId(\\d+)/phases') +router.route('/v5/projects/:projectId(\\d+)/phases') .get(require('./phases/list')) .post(require('./phases/create')); -router.route('/v4/projects/:projectId(\\d+)/phases/db') +router.route('/v5/projects/:projectId(\\d+)/phases/db') .get(require('./phases/list-db')); -router.route('/v4/projects/:projectId(\\d+)/phases/:phaseId(\\d+)') +router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)') .get(require('./phases/get')) .patch(require('./phases/update')) .delete(require('./phases/delete')); -router.route('/v4/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products') +router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products') .get(require('./phaseProducts/list')) .post(require('./phaseProducts/create')); -router.route('/v4/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products/db') +router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products/db') .get(require('./phaseProducts/list-db')); -router.route('/v4/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products/:productId(\\d+)') +router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products/:productId(\\d+)') .get(require('./phaseProducts/get')) .patch(require('./phaseProducts/update')) .delete(require('./phaseProducts/delete')); -router.route('/v4/projects/metadata/productCategories') +router.route('/v5/projects/metadata/productCategories') .post(require('./productCategories/create')); -router.route('/v4/projects/metadata/productCategories/:key') +router.route('/v5/projects/metadata/productCategories/:key') .patch(require('./productCategories/update')) .delete(require('./productCategories/delete')); -router.route('/v4/projects/metadata/projectTypes') +router.route('/v5/projects/metadata/projectTypes') .post(require('./projectTypes/create')); -router.route('/v4/projects/metadata/projectTypes/:key') +router.route('/v5/projects/metadata/projectTypes/:key') .patch(require('./projectTypes/update')) .delete(require('./projectTypes/delete')); -router.route('/v4/timelines') +router.route('/v5/timelines') .post(require('./timelines/create')) .get(require('./timelines/list')); -router.route('/v4/timelines/:timelineId(\\d+)') +router.route('/v5/timelines/:timelineId(\\d+)') .get(require('./timelines/get')) .patch(require('./timelines/update')) .delete(require('./timelines/delete')); -router.route('/v4/timelines/:timelineId(\\d+)/milestones') +router.route('/v5/timelines/:timelineId(\\d+)/milestones') .post(require('./milestones/create')) .get(require('./milestones/list')); -router.route('/v4/timelines/:timelineId(\\d+)/milestones/:milestoneId(\\d+)') +router.route('/v5/timelines/:timelineId(\\d+)/milestones/:milestoneId(\\d+)') .get(require('./milestones/get')) .patch(require('./milestones/update')) .delete(require('./milestones/delete')); -router.route('/v4/timelines/metadata/milestoneTemplates') +router.route('/v5/timelines/metadata/milestoneTemplates') .post(require('./milestoneTemplates/create')) .get(require('./milestoneTemplates/list')); -router.route('/v4/timelines/metadata/milestoneTemplates/clone') +router.route('/v5/timelines/metadata/milestoneTemplates/clone') .post(require('./milestoneTemplates/clone')); -router.route('/v4/timelines/metadata/milestoneTemplates/:milestoneTemplateId(\\d+)') +router.route('/v5/timelines/metadata/milestoneTemplates/:milestoneTemplateId(\\d+)') .get(require('./milestoneTemplates/get')) .patch(require('./milestoneTemplates/update')) .delete(require('./milestoneTemplates/delete')); -router.route('/v4/projects/:projectId(\\d+)/members/invite') +router.route('/v5/projects/:projectId(\\d+)/members/invite') .post(require('./projectMemberInvites/create')) .put(require('./projectMemberInvites/update')) .get(require('./projectMemberInvites/get')); -router.route('/v4/projects/metadata/orgConfig') +router.route('/v5/projects/metadata/orgConfig') .post(require('./orgConfig/create')); -router.route('/v4/projects/metadata/orgConfig/:id(\\d+)') +router.route('/v5/projects/metadata/orgConfig/:id(\\d+)') .patch(require('./orgConfig/update')) .delete(require('./orgConfig/delete')); // form -router.route('/v4/projects/metadata/form/:key/versions/:version(\\d+)/revisions/:revision(\\d+)') +router.route('/v5/projects/metadata/form/:key/versions/:version(\\d+)/revisions/:revision(\\d+)') .get(require('./form/revision/get')) .delete(require('./form/revision/delete')); -router.route('/v4/projects/metadata/form/:key/versions/:version(\\d+)/revisions') +router.route('/v5/projects/metadata/form/:key/versions/:version(\\d+)/revisions') .get(require('./form/revision/list')) .post(require('./form/revision/create')); -router.route('/v4/projects/metadata/form/:key') +router.route('/v5/projects/metadata/form/:key') .get(require('./form/version/get')); -router.route('/v4/projects/metadata/form/:key/versions') +router.route('/v5/projects/metadata/form/:key/versions') .get(require('./form/version/list')) .post(require('./form/version/create')); -router.route('/v4/projects/metadata/form/:key/versions/:version(\\d+)') +router.route('/v5/projects/metadata/form/:key/versions/:version(\\d+)') .get(require('./form/version/getVersion')) .patch(require('./form/version/update')) .delete(require('./form/version/delete')); // price config -router.route('/v4/projects/metadata/priceConfig/:key/versions/:version(\\d+)/revisions/:revision(\\d+)') +router.route('/v5/projects/metadata/priceConfig/:key/versions/:version(\\d+)/revisions/:revision(\\d+)') .get(require('./priceConfig/revision/get')) .delete(require('./priceConfig/revision/delete')); -router.route('/v4/projects/metadata/priceConfig/:key/versions/:version(\\d+)/revisions') +router.route('/v5/projects/metadata/priceConfig/:key/versions/:version(\\d+)/revisions') .get(require('./priceConfig/revision/list')) .post(require('./priceConfig/revision/create')); -router.route('/v4/projects/metadata/priceConfig/:key') +router.route('/v5/projects/metadata/priceConfig/:key') .get(require('./priceConfig/version/get')); -router.route('/v4/projects/metadata/priceConfig/:key/versions') +router.route('/v5/projects/metadata/priceConfig/:key/versions') .get(require('./priceConfig/version/list')) .post(require('./priceConfig/version/create')); -router.route('/v4/projects/metadata/priceConfig/:key/versions/:version(\\d+)') +router.route('/v5/projects/metadata/priceConfig/:key/versions/:version(\\d+)') .get(require('./priceConfig/version/getVersion')) .patch(require('./priceConfig/version/update')) .delete(require('./priceConfig/version/delete')); // plan config -router.route('/v4/projects/metadata/planConfig/:key/versions/:version(\\d+)/revisions/:revision(\\d+)') +router.route('/v5/projects/metadata/planConfig/:key/versions/:version(\\d+)/revisions/:revision(\\d+)') .get(require('./planConfig/revision/get')) .delete(require('./planConfig/revision/delete')); -router.route('/v4/projects/metadata/planConfig/:key/versions/:version(\\d+)/revisions') +router.route('/v5/projects/metadata/planConfig/:key/versions/:version(\\d+)/revisions') .get(require('./planConfig/revision/list')) .post(require('./planConfig/revision/create')); -router.route('/v4/projects/metadata/planConfig/:key') +router.route('/v5/projects/metadata/planConfig/:key') .get(require('./planConfig/version/get')); -router.route('/v4/projects/metadata/planConfig/:key/versions') +router.route('/v5/projects/metadata/planConfig/:key/versions') .get(require('./planConfig/version/list')) .post(require('./planConfig/version/create')); -router.route('/v4/projects/metadata/planConfig/:key/versions/:version(\\d+)') +router.route('/v5/projects/metadata/planConfig/:key/versions/:version(\\d+)') .get(require('./planConfig/version/getVersion')) .patch(require('./planConfig/version/update')) .delete(require('./planConfig/version/delete')); @@ -276,35 +276,26 @@ router.use((err, req, res, next) => { // eslint-disable-line no-unused-vars // DO NOT REMOVE next arg.. even though eslint // complains that it is not being used. const content = {}; - let httpStatus = err.status || 500; // specific for validation errors if (err instanceof validate.ValidationError) { content.message = `${err.message}: ${err.toJSON()}`; - httpStatus = err.status; } else { content.message = err.message; } - const body = { - id: req.id, - result: { - success: false, - status: httpStatus, - content, - }, - }; // dvalidateelopment error handler // will print stacktrace if (_.indexOf(['development', 'test', 'qa'], process.env.NODE_ENV) > -1) { - body.result.debug = err.stack; + // body.result.debug = err.stack; + content.debug = err.stack; if (err.details) { - body.result.details = err.details; + content.details = err.details; } } const rerr = err; rerr.status = rerr.status || 500; req.log.error(rerr); - res.status(rerr.status).send(body); + res.status(rerr.status).send(content); }); // catch 404 and forward to error handler diff --git a/src/routes/metadata/list.js b/src/routes/metadata/list.js index 3358350f..d2b239c2 100644 --- a/src/routes/metadata/list.js +++ b/src/routes/metadata/list.js @@ -4,7 +4,6 @@ import { middleware as tcMiddleware } from 'tc-core-library-js'; import Joi from 'joi'; import validate from 'express-validation'; -import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; @@ -103,7 +102,7 @@ module.exports = [ Promise.resolve(latestVersion[2]), ]); }).then((queryAllResult) => { - res.json(util.wrapResponse(req.id, { + res.json({ projectTemplates: queryAllResult[0], productTemplates: queryAllResult[1], milestoneTemplates: queryAllResult[2], @@ -112,7 +111,7 @@ module.exports = [ forms: queryAllResult[5], priceConfigs: queryAllResult[6], planConfigs: queryAllResult[7], - })); + }); }) .catch(next); } @@ -127,7 +126,7 @@ module.exports = [ models.PlanConfig.latestVersion(), ]) .then((results) => { - res.json(util.wrapResponse(req.id, { + res.json({ projectTemplates: results[0], productTemplates: results[1], milestoneTemplates: results[2], @@ -136,7 +135,7 @@ module.exports = [ forms: results[5], priceConfigs: results[6], planConfigs: results[7], - })); + }); }) .catch(next); }, diff --git a/src/routes/metadata/list.spec.js b/src/routes/metadata/list.spec.js index 6bbf9cb8..762758e4 100644 --- a/src/routes/metadata/list.spec.js +++ b/src/routes/metadata/list.spec.js @@ -179,7 +179,8 @@ const planConfigs = [ ]; describe('GET all metadata', () => { - beforeEach(() => testUtil.clearDb() + before((done) => { + testUtil.clearDb() .then(() => models.ProjectTemplate.bulkCreate(projectTemplates)) .then(() => models.ProductTemplate.bulkCreate(productTemplates)) .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)) @@ -187,20 +188,23 @@ describe('GET all metadata', () => { .then(() => models.ProductCategory.bulkCreate(productCategories)) .then(() => models.Form.bulkCreate(forms)) .then(() => models.PriceConfig.bulkCreate(priceConfigs)) - .then(() => models.PlanConfig.bulkCreate(planConfigs)), - ); - after(testUtil.clearDb); + .then(() => models.PlanConfig.bulkCreate(planConfigs).then(() => done())); + }); + + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata') + .get('/v5/projects/metadata') .expect(403, done); }); it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata') + .get('/v5/projects/metadata') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -210,13 +214,13 @@ describe('GET all metadata', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata') + .get('/v5/projects/metadata') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.projectTemplates.should.have.length(1); resJson.productTemplates.should.have.length(1); @@ -237,13 +241,13 @@ describe('GET all metadata', () => { it('should return all used model when request with includeAllReferred query', (done) => { request(server) - .get('/v4/projects/metadata?includeAllReferred=true') + .get('/v5/projects/metadata?includeAllReferred=true') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.projectTemplates.should.have.length(1); resJson.productTemplates.should.have.length(1); @@ -259,7 +263,7 @@ describe('GET all metadata', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata') + .get('/v5/projects/metadata') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -269,7 +273,7 @@ describe('GET all metadata', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata') + .get('/v5/projects/metadata') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -278,7 +282,7 @@ describe('GET all metadata', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata') + .get('/v5/projects/metadata') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/milestoneTemplates/clone.spec.js b/src/routes/milestoneTemplates/clone.spec.js index a0f9bbbd..7fde9b3f 100644 --- a/src/routes/milestoneTemplates/clone.spec.js +++ b/src/routes/milestoneTemplates/clone.spec.js @@ -113,11 +113,15 @@ const milestoneTemplates = [ ]; describe('CLONE milestone template', () => { - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.ProductTemplate.bulkCreate(productTemplates)) - .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)), + .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); }); + }, ); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /timelines/metadata/milestoneTemplates/clone', () => { const body = { @@ -131,14 +135,14 @@ describe('CLONE milestone template', () => { it('should return 403 if user is not authenticated/clone', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates/clone') + .post('/v5/timelines/metadata/milestoneTemplates/clone') .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates/clone') + .post('/v5/timelines/metadata/milestoneTemplates/clone') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -148,7 +152,7 @@ describe('CLONE milestone template', () => { it('should return 403 for copilot', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates/clone') + .post('/v5/timelines/metadata/milestoneTemplates/clone') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -158,7 +162,7 @@ describe('CLONE milestone template', () => { it('should return 403 for manager', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates/clone') + .post('/v5/timelines/metadata/milestoneTemplates/clone') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -166,7 +170,7 @@ describe('CLONE milestone template', () => { .expect(403, done); }); - it('should return 422 for non-existent product template', (done) => { + it('should return 400 for non-existent product template', (done) => { const invalidBody = { param: { sourceReference: 'productTemplate', @@ -177,15 +181,15 @@ describe('CLONE milestone template', () => { }; request(server) - .post('/v4/timelines/metadata/milestoneTemplates/clone') + .post('/v5/timelines/metadata/milestoneTemplates/clone') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for non-existent source product template', (done) => { + it('should return 400 for non-existent source product template', (done) => { const invalidBody = { param: { sourceReference: 'product', @@ -196,15 +200,15 @@ describe('CLONE milestone template', () => { }; request(server) - .post('/v4/timelines/metadata/milestoneTemplates/clone') + .post('/v5/timelines/metadata/milestoneTemplates/clone') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing sourceReference', (done) => { + it('should return 400 if missing sourceReference', (done) => { const invalidBody = { param: { sourceReferenceId: 1000, @@ -214,18 +218,18 @@ describe('CLONE milestone template', () => { }; request(server) - .post('/v4/timelines/metadata/milestoneTemplates/clone') + .post('/v5/timelines/metadata/milestoneTemplates/clone') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates/clone') + .post('/v5/timelines/metadata/milestoneTemplates/clone') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -260,7 +264,7 @@ describe('CLONE milestone template', () => { it('should return 201 for connect admin', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates/clone') + .post('/v5/timelines/metadata/milestoneTemplates/clone') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/milestoneTemplates/create.js b/src/routes/milestoneTemplates/create.js index 321c9b65..ad9018cb 100644 --- a/src/routes/milestoneTemplates/create.js +++ b/src/routes/milestoneTemplates/create.js @@ -50,7 +50,6 @@ module.exports = [ updatedBy: req.authUser.userId, }); let result; - return models.sequelize.transaction(tx => // Create the milestone template models.MilestoneTemplate.create(entity, { transaction: tx }) diff --git a/src/routes/milestoneTemplates/create.spec.js b/src/routes/milestoneTemplates/create.spec.js index beacf71f..9483ad11 100644 --- a/src/routes/milestoneTemplates/create.spec.js +++ b/src/routes/milestoneTemplates/create.spec.js @@ -94,11 +94,15 @@ const milestoneTemplates = [ ]; describe('CREATE milestone template', () => { - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.ProductTemplate.bulkCreate(productTemplates)) - .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)), + .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); }); + }, ); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /timelines/metadata/milestoneTemplates', () => { const body = { @@ -121,14 +125,14 @@ describe('CREATE milestone template', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -138,7 +142,7 @@ describe('CREATE milestone template', () => { it('should return 403 for copilot', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -148,7 +152,7 @@ describe('CREATE milestone template', () => { it('should return 403 for manager', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -156,21 +160,21 @@ describe('CREATE milestone template', () => { .expect(403, done); }); - it('should return 422 for non-existed product template', (done) => { + it('should return 400 for non-existed product template', (done) => { const invalidBody = { param: _.assign({}, body.param, { referenceId: 1000 }), }; request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing name', (done) => { + it('should return 400 if missing name', (done) => { const invalidBody = { param: { name: undefined, @@ -178,16 +182,16 @@ describe('CREATE milestone template', () => { }; request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing duration', (done) => { + it('should return 400 if missing duration', (done) => { const invalidBody = { param: { duration: undefined, @@ -195,16 +199,16 @@ describe('CREATE milestone template', () => { }; request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing type', (done) => { + it('should return 400 if missing type', (done) => { const invalidBody = { param: { type: undefined, @@ -212,16 +216,16 @@ describe('CREATE milestone template', () => { }; request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing order', (done) => { + it('should return 400 if missing order', (done) => { const invalidBody = { param: { order: undefined, @@ -229,18 +233,18 @@ describe('CREATE milestone template', () => { }; request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -295,7 +299,7 @@ describe('CREATE milestone template', () => { const minimalBody = _.cloneDeep(body); delete minimalBody.param.hidden; request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -311,7 +315,7 @@ describe('CREATE milestone template', () => { it('should return 201 for connect admin', (done) => { request(server) - .post('/v4/timelines/metadata/milestoneTemplates') + .post('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/milestoneTemplates/delete.spec.js b/src/routes/milestoneTemplates/delete.spec.js index 92a6dcb6..25f61ad4 100644 --- a/src/routes/milestoneTemplates/delete.spec.js +++ b/src/routes/milestoneTemplates/delete.spec.js @@ -26,7 +26,7 @@ const expectAfterDelete = (id, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/timelines/metadata/milestoneTemplates/${id}`) + .get(`/v5/timelines/metadata/milestoneTemplates/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -122,22 +122,26 @@ const milestoneTemplates = [ ]; describe('DELETE milestone template', () => { - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.ProductTemplate.bulkCreate(productTemplates)) - .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)), + .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); }); + }, ); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /timelines/metadata/milestoneTemplates/{milestoneTemplateId}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete('/v4/timelines/metadata/milestoneTemplates/1') + .delete('/v5/timelines/metadata/milestoneTemplates/1') .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete('/v4/timelines/metadata/milestoneTemplates/1') + .delete('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -146,7 +150,7 @@ describe('DELETE milestone template', () => { it('should return 403 for copilot', (done) => { request(server) - .delete('/v4/timelines/metadata/milestoneTemplates/1') + .delete('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -155,7 +159,7 @@ describe('DELETE milestone template', () => { it('should return 403 for manager', (done) => { request(server) - .delete('/v4/timelines/metadata/milestoneTemplates/1') + .delete('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -164,7 +168,7 @@ describe('DELETE milestone template', () => { it('should return 404 for non-existed milestone template', (done) => { request(server) - .delete('/v4/timelines/metadata/milestoneTemplates/444') + .delete('/v5/timelines/metadata/milestoneTemplates/444') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -173,25 +177,25 @@ describe('DELETE milestone template', () => { it('should return 404 for deleted milestone template', (done) => { request(server) - .delete('/v4/timelines/metadata/milestoneTemplates/2') + .delete('/v5/timelines/metadata/milestoneTemplates/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(404, done); }); - it('should return 422 for invalid milestoneTemplateId param', (done) => { + it('should return 400 for invalid milestoneTemplateId param', (done) => { request(server) - .delete('/v4/timelines/metadata/milestoneTemplates/0') + .delete('/v5/timelines/metadata/milestoneTemplates/0') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); it('should return 204, for admin, if template was successfully removed', (done) => { request(server) - .delete('/v4/timelines/metadata/milestoneTemplates/1') + .delete('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -201,7 +205,7 @@ describe('DELETE milestone template', () => { it('should return 204, for connect admin, if template was successfully removed', (done) => { request(server) - .delete('/v4/timelines/metadata/milestoneTemplates/1') + .delete('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/milestoneTemplates/get.spec.js b/src/routes/milestoneTemplates/get.spec.js index 50f31370..242e0960 100644 --- a/src/routes/milestoneTemplates/get.spec.js +++ b/src/routes/milestoneTemplates/get.spec.js @@ -97,22 +97,26 @@ const milestoneTemplates = [ ]; describe('GET milestone template', () => { - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.ProductTemplate.bulkCreate(productTemplates)) - .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)), + .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); }); + }, ); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /timelines/metadata/milestoneTemplates/{milestoneTemplateId}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates/1') + .get('/v5/timelines/metadata/milestoneTemplates/1') .expect(403, done); }); it('should return 404 for non-existed milestone template', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates/1111') + .get('/v5/timelines/metadata/milestoneTemplates/1111') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -121,7 +125,7 @@ describe('GET milestone template', () => { it('should return 404 for deleted milestone template', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates/2') + .get('/v5/timelines/metadata/milestoneTemplates/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -130,7 +134,7 @@ describe('GET milestone template', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates/1') + .get('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -163,7 +167,7 @@ describe('GET milestone template', () => { it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates/1') + .get('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -173,7 +177,7 @@ describe('GET milestone template', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates/1') + .get('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -183,7 +187,7 @@ describe('GET milestone template', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates/1') + .get('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -192,7 +196,7 @@ describe('GET milestone template', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates/1') + .get('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/milestoneTemplates/list.js b/src/routes/milestoneTemplates/list.js index 64d93acf..1fc737c5 100644 --- a/src/routes/milestoneTemplates/list.js +++ b/src/routes/milestoneTemplates/list.js @@ -23,7 +23,7 @@ module.exports = [ ]; if (sort && _.indexOf(sortableProps, sort) < 0) { const apiErr = new Error('Invalid sort criteria'); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } const sortColumnAndOrder = sort.split(' '); diff --git a/src/routes/milestoneTemplates/list.spec.js b/src/routes/milestoneTemplates/list.spec.js index 465a388f..0fc02db3 100644 --- a/src/routes/milestoneTemplates/list.spec.js +++ b/src/routes/milestoneTemplates/list.spec.js @@ -113,40 +113,44 @@ const milestoneTemplates = [ ]; describe('LIST milestone template', () => { - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.ProductTemplate.bulkCreate(productTemplates)) - .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)), + .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); }); + }, ); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /timelines/metadata/milestoneTemplates', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates') + .get('/v5/timelines/metadata/milestoneTemplates') .expect(403, done); }); - it('should return 422 for invalid sort column', (done) => { + it('should return 400 for invalid sort column', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates?sort=id') + .get('/v5/timelines/metadata/milestoneTemplates?sort=id') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for invalid sort order', (done) => { + it('should return 400 for invalid sort order', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates?sort=order%20invalid') + .get('/v5/timelines/metadata/milestoneTemplates?sort=order%20invalid') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); it('should return 200 for admin', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates') + .get('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -180,7 +184,7 @@ describe('LIST milestone template', () => { it('should return 200 for connect admin with filter', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1') + .get('/v5/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -190,7 +194,7 @@ describe('LIST milestone template', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates') + .get('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -200,7 +204,7 @@ describe('LIST milestone template', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates') + .get('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -209,7 +213,7 @@ describe('LIST milestone template', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates') + .get('/v5/timelines/metadata/milestoneTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -218,7 +222,7 @@ describe('LIST milestone template', () => { it('should return 200 with sort desc', (done) => { request(server) - .get('/v4/timelines/metadata/milestoneTemplates?sort=order%20desc') + .get('/v5/timelines/metadata/milestoneTemplates?sort=order%20desc') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/milestoneTemplates/update.spec.js b/src/routes/milestoneTemplates/update.spec.js index 3de8c430..6eb462c1 100644 --- a/src/routes/milestoneTemplates/update.spec.js +++ b/src/routes/milestoneTemplates/update.spec.js @@ -160,11 +160,16 @@ const milestoneTemplates = [ ]; describe('UPDATE milestone template', () => { - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.ProductTemplate.bulkCreate(productTemplates)) - .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)), + .then(() => { models.MilestoneTemplate.bulkCreate(milestoneTemplates).then(() => done()); }); + }, ); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); + describe('PATCH /timelines/metadata/milestoneTemplates/{milestoneTemplateId}', () => { const body = { @@ -187,14 +192,14 @@ describe('UPDATE milestone template', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -204,7 +209,7 @@ describe('UPDATE milestone template', () => { it('should return 403 for copilot', (done) => { request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, @@ -214,7 +219,7 @@ describe('UPDATE milestone template', () => { it('should return 403 for manager', (done) => { request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, @@ -224,7 +229,7 @@ describe('UPDATE milestone template', () => { it('should return 404 for non-existed milestone template', (done) => { request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/111') + .patch('/v5/timelines/metadata/milestoneTemplates/111') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -234,7 +239,7 @@ describe('UPDATE milestone template', () => { it('should return 404 for deleted milestone template', (done) => { request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/4') + .patch('/v5/timelines/metadata/milestoneTemplates/4') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -244,7 +249,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for admin', (done) => { request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -282,7 +287,7 @@ describe('UPDATE milestone template', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -292,15 +297,15 @@ describe('UPDATE milestone template', () => { // Milestone 1: order 3 // Milestone 2: order 2 - 1 = 1 // Milestone 3: order 3 - 1 = 2 - models.MilestoneTemplate.findById(1) + models.MilestoneTemplate.findByPk(1) .then((milestone) => { milestone.order.should.be.eql(3); }) - .then(() => models.MilestoneTemplate.findById(2)) + .then(() => models.MilestoneTemplate.findByPk(2)) .then((milestone) => { milestone.order.should.be.eql(1); }) - .then(() => models.MilestoneTemplate.findById(3)) + .then(() => models.MilestoneTemplate.findByPk(3)) .then((milestone) => { milestone.order.should.be.eql(2); @@ -314,7 +319,7 @@ describe('UPDATE milestone template', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -324,15 +329,15 @@ describe('UPDATE milestone template', () => { // Milestone 1: order 4 // Milestone 2: order 2 // Milestone 3: order 3 - models.MilestoneTemplate.findById(1) + models.MilestoneTemplate.findByPk(1) .then((milestone) => { milestone.order.should.be.eql(4); }) - .then(() => models.MilestoneTemplate.findById(2)) + .then(() => models.MilestoneTemplate.findByPk(2)) .then((milestone) => { milestone.order.should.be.eql(2); }) - .then(() => models.MilestoneTemplate.findById(3)) + .then(() => models.MilestoneTemplate.findByPk(3)) .then((milestone) => { milestone.order.should.be.eql(3); @@ -346,7 +351,7 @@ describe('UPDATE milestone template', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/3') + .patch('/v5/timelines/metadata/milestoneTemplates/3') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -356,15 +361,15 @@ describe('UPDATE milestone template', () => { // Milestone 1: order 2 // Milestone 2: order 3 // Milestone 3: order 1 - models.MilestoneTemplate.findById(1) + models.MilestoneTemplate.findByPk(1) .then((milestone) => { milestone.order.should.be.eql(2); }) - .then(() => models.MilestoneTemplate.findById(2)) + .then(() => models.MilestoneTemplate.findByPk(2)) .then((milestone) => { milestone.order.should.be.eql(3); }) - .then(() => models.MilestoneTemplate.findById(3)) + .then(() => models.MilestoneTemplate.findByPk(3)) .then((milestone) => { milestone.order.should.be.eql(1); @@ -378,7 +383,7 @@ describe('UPDATE milestone template', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/3') + .patch('/v5/timelines/metadata/milestoneTemplates/3') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -388,15 +393,15 @@ describe('UPDATE milestone template', () => { // Milestone 1: order 1 // Milestone 2: order 2 // Milestone 3: order 0 - models.MilestoneTemplate.findById(1) + models.MilestoneTemplate.findByPk(1) .then((milestone) => { milestone.order.should.be.eql(1); }) - .then(() => models.MilestoneTemplate.findById(2)) + .then(() => models.MilestoneTemplate.findByPk(2)) .then((milestone) => { milestone.order.should.be.eql(2); }) - .then(() => models.MilestoneTemplate.findById(3)) + .then(() => models.MilestoneTemplate.findByPk(3)) .then((milestone) => { milestone.order.should.be.eql(0); @@ -409,7 +414,7 @@ describe('UPDATE milestone template', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.name; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -421,7 +426,7 @@ describe('UPDATE milestone template', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.type; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -433,7 +438,7 @@ describe('UPDATE milestone template', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.duration; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -445,7 +450,7 @@ describe('UPDATE milestone template', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.order; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -457,7 +462,7 @@ describe('UPDATE milestone template', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.plannedText; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -469,7 +474,7 @@ describe('UPDATE milestone template', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.blockedText; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -481,7 +486,7 @@ describe('UPDATE milestone template', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.activeText; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -493,7 +498,7 @@ describe('UPDATE milestone template', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.completedText; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -505,7 +510,7 @@ describe('UPDATE milestone template', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.hidden; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -515,7 +520,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for connect admin', (done) => { request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/1') + .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -560,7 +565,7 @@ describe('UPDATE milestone template', () => { }; request(server) - .patch('/v4/timelines/metadata/milestoneTemplates/5') + .patch('/v5/timelines/metadata/milestoneTemplates/5') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/milestones/create.js b/src/routes/milestones/create.js index eadec1f4..b4409b8e 100644 --- a/src/routes/milestones/create.js +++ b/src/routes/milestones/create.js @@ -69,7 +69,7 @@ module.exports = [ } if (error) { const apiErr = new Error(error); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } diff --git a/src/routes/milestones/create.spec.js b/src/routes/milestones/create.spec.js index ab1424ca..606cfda5 100644 --- a/src/routes/milestones/create.spec.js +++ b/src/routes/milestones/create.spec.js @@ -200,7 +200,9 @@ describe('CREATE milestone', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /timelines/{timelineId}/milestones', () => { const body = { @@ -234,14 +236,14 @@ describe('CREATE milestone', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .send(body) .expect(403, done); }); it('should return 403 for member who is not in the project', (done) => { request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -249,7 +251,7 @@ describe('CREATE milestone', () => { .expect(403, done); }); - it('should return 422 if missing name', (done) => { + it('should return 400 if missing name', (done) => { const invalidBody = { param: _.assign({}, body.param, { name: undefined, @@ -257,16 +259,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing duration', (done) => { + it('should return 400 if missing duration', (done) => { const invalidBody = { param: _.assign({}, body.param, { duration: undefined, @@ -274,16 +276,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing type', (done) => { + it('should return 400 if missing type', (done) => { const invalidBody = { param: _.assign({}, body.param, { type: undefined, @@ -291,16 +293,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing order', (done) => { + it('should return 400 if missing order', (done) => { const invalidBody = { param: _.assign({}, body.param, { order: undefined, @@ -308,16 +310,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing plannedText', (done) => { + it('should return 400 if missing plannedText', (done) => { const invalidBody = { param: _.assign({}, body.param, { plannedText: undefined, @@ -325,16 +327,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing activeText', (done) => { + it('should return 400 if missing activeText', (done) => { const invalidBody = { param: _.assign({}, body.param, { activeText: undefined, @@ -342,16 +344,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing completedText', (done) => { + it('should return 400 if missing completedText', (done) => { const invalidBody = { param: _.assign({}, body.param, { completedText: undefined, @@ -359,16 +361,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing blockedText', (done) => { + it('should return 400 if missing blockedText', (done) => { const invalidBody = { param: _.assign({}, body.param, { blockedText: undefined, @@ -376,16 +378,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if startDate is after endDate', (done) => { + it('should return 400 if startDate is after endDate', (done) => { const invalidBody = { param: _.assign({}, body.param, { startDate: '2018-05-29T00:00:00.000Z', @@ -394,16 +396,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if startDate is after completionDate', (done) => { + it('should return 400 if startDate is after completionDate', (done) => { const invalidBody = { param: _.assign({}, body.param, { startDate: '2018-05-29T00:00:00.000Z', @@ -412,16 +414,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if startDate is before the timeline startDate', (done) => { + it('should return 400 if startDate is before the timeline startDate', (done) => { const invalidBody = { param: _.assign({}, body.param, { startDate: '2018-05-01T00:00:00.000Z', @@ -429,16 +431,16 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if endDate is after the timeline endDate', (done) => { + it('should return 400 if endDate is after the timeline endDate', (done) => { const invalidBody = { param: _.assign({}, body.param, { endDate: '2018-06-13T00:00:00.000Z', @@ -446,29 +448,29 @@ describe('CREATE milestone', () => { }; request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if invalid timelineId param', (done) => { + it('should return 400 if invalid timelineId param', (done) => { request(server) - .post('/v4/timelines/0/milestones') + .post('/v5/timelines/0/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 404 if timeline does not exist', (done) => { request(server) - .post('/v4/timelines/1000/milestones') + .post('/v5/timelines/1000/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -479,7 +481,7 @@ describe('CREATE milestone', () => { it('should return 404 if timeline was deleted', (done) => { request(server) - .post('/v4/timelines/3/milestones') + .post('/v5/timelines/3/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -490,7 +492,7 @@ describe('CREATE milestone', () => { it('should return 201 for admin', (done) => { request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -546,7 +548,7 @@ describe('CREATE milestone', () => { it('should return 201 for connect manager', (done) => { request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -563,7 +565,7 @@ describe('CREATE milestone', () => { it('should return 201 for connect admin', (done) => { request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -580,7 +582,7 @@ describe('CREATE milestone', () => { it('should return 201 for copilot', (done) => { request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -597,7 +599,7 @@ describe('CREATE milestone', () => { it('should return 201 for member', (done) => { request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -631,7 +633,7 @@ describe('CREATE milestone', () => { it('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when milestone created', (done) => { request(server) - .post('/v4/timelines/1/milestones') + .post('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/milestones/delete.spec.js b/src/routes/milestones/delete.spec.js index c756b7b0..2ee6010f 100644 --- a/src/routes/milestones/delete.spec.js +++ b/src/routes/milestones/delete.spec.js @@ -29,7 +29,7 @@ const expectAfterDelete = (timelineId, id, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/timelines/${timelineId}/milestones/${id}`) + .get(`/v5/timelines/${timelineId}/milestones/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -220,18 +220,20 @@ describe('DELETE milestone', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /timelines/{timelineId}/milestones/{milestoneId}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete('/v4/timelines/1/milestones/1') + .delete('/v5/timelines/1/milestones/1') .expect(403, done); }); it('should return 403 for member who is not in the project', (done) => { request(server) - .delete('/v4/timelines/1/milestones/1') + .delete('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -240,7 +242,7 @@ describe('DELETE milestone', () => { it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => { request(server) - .delete('/v4/timelines/2/milestones/1') + .delete('/v5/timelines/2/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -249,7 +251,7 @@ describe('DELETE milestone', () => { it('should return 404 for non-existed timeline', (done) => { request(server) - .delete('/v4/timelines/1234/milestones/1') + .delete('/v5/timelines/1234/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -258,7 +260,7 @@ describe('DELETE milestone', () => { it('should return 404 for deleted timeline', (done) => { request(server) - .delete('/v4/timelines/3/milestones/1') + .delete('/v5/timelines/3/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -267,7 +269,7 @@ describe('DELETE milestone', () => { it('should return 404 for non-existed milestone', (done) => { request(server) - .delete('/v4/timelines/1/milestones/100') + .delete('/v5/timelines/1/milestones/100') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -276,34 +278,34 @@ describe('DELETE milestone', () => { it('should return 404 for deleted milestone', (done) => { request(server) - .delete('/v4/timelines/1/milestones/3') + .delete('/v5/timelines/1/milestones/3') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(404, done); }); - it('should return 422 for invalid timelineId param', (done) => { + it('should return 400 for invalid timelineId param', (done) => { request(server) - .delete('/v4/timelines/0/milestones/1') + .delete('/v5/timelines/0/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for invalid milestoneId param', (done) => { + it('should return 400 for invalid milestoneId param', (done) => { request(server) - .delete('/v4/timelines/1/milestones/0') + .delete('/v5/timelines/1/milestones/0') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); it('should return 204, for admin, if timeline was successfully removed', (done) => { request(server) - .delete('/v4/timelines/1/milestones/1') + .delete('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -317,7 +319,7 @@ describe('DELETE milestone', () => { it('should return 204, for connect admin, if timeline was successfully removed', (done) => { request(server) - .delete('/v4/timelines/1/milestones/1') + .delete('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -327,7 +329,7 @@ describe('DELETE milestone', () => { it('should return 204, for connect manager, if timeline was successfully removed', (done) => { request(server) - .delete('/v4/timelines/1/milestones/1') + .delete('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -337,7 +339,7 @@ describe('DELETE milestone', () => { it('should return 204, for copilot, if timeline was successfully removed', (done) => { request(server) - .delete('/v4/timelines/1/milestones/1') + .delete('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -347,7 +349,7 @@ describe('DELETE milestone', () => { it('should return 204, for member, if timeline was successfully removed', (done) => { request(server) - .delete('/v4/timelines/1/milestones/1') + .delete('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -376,7 +378,7 @@ describe('DELETE milestone', () => { // thus TIMELINE_ADJUSTED will be always sent it('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when milestone removed', (done) => { request(server) - .delete('/v4/timelines/1/milestones/1') + .delete('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/milestones/get.spec.js b/src/routes/milestones/get.spec.js index fb0451d1..ebbfb9d7 100644 --- a/src/routes/milestones/get.spec.js +++ b/src/routes/milestones/get.spec.js @@ -193,18 +193,20 @@ describe('GET milestone', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /timelines/{timelineId}/milestones/{milestoneId}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/timelines/1/milestones/1') + .get('/v5/timelines/1/milestones/1') .expect(403, done); }); it('should return 403 for member who is not in the project', (done) => { request(server) - .get('/v4/timelines/1/milestones/1') + .get('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -213,7 +215,7 @@ describe('GET milestone', () => { it('should return 404 for non-existed timeline', (done) => { request(server) - .get('/v4/timelines/1234/milestones/1') + .get('/v5/timelines/1234/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -222,7 +224,7 @@ describe('GET milestone', () => { it('should return 404 for deleted timeline', (done) => { request(server) - .get('/v4/timelines/3/milestones/1') + .get('/v5/timelines/3/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -231,7 +233,7 @@ describe('GET milestone', () => { it('should return 404 for non-existed milestone', (done) => { request(server) - .get('/v4/timelines/1/milestones/1234') + .get('/v5/timelines/1/milestones/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -240,34 +242,34 @@ describe('GET milestone', () => { it('should return 404 for deleted milestone', (done) => { request(server) - .get('/v4/timelines/1/milestones/3') + .get('/v5/timelines/1/milestones/3') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(404, done); }); - it('should return 422 for invalid timelineId param', (done) => { + it('should return 400 for invalid timelineId param', (done) => { request(server) - .get('/v4/timelines/0/milestones/3') + .get('/v5/timelines/0/milestones/3') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for invalid milestoneId param', (done) => { + it('should return 400 for invalid milestoneId param', (done) => { request(server) - .get('/v4/timelines/1/milestones/0') + .get('/v5/timelines/1/milestones/0') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); it('should return 200 for admin', (done) => { request(server) - .get('/v4/timelines/1/milestones/1') + .get('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -307,7 +309,7 @@ describe('GET milestone', () => { it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/timelines/1/milestones/1') + .get('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -317,7 +319,7 @@ describe('GET milestone', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/timelines/1/milestones/1') + .get('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -327,7 +329,7 @@ describe('GET milestone', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/timelines/1/milestones/1') + .get('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -336,7 +338,7 @@ describe('GET milestone', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/timelines/1/milestones/1') + .get('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/milestones/list.js b/src/routes/milestones/list.js index 16152f50..54197b01 100644 --- a/src/routes/milestones/list.js +++ b/src/routes/milestones/list.js @@ -37,7 +37,7 @@ module.exports = [ ]; if (sort && _.indexOf(sortableProps, sort) < 0) { const apiErr = new Error('Invalid sort criteria'); - apiErr.status = 422; + apiErr.status = 400; return next(apiErr); } const sortColumnAndOrder = sort.split(' '); diff --git a/src/routes/milestones/list.spec.js b/src/routes/milestones/list.spec.js index 21c07336..04c66a30 100644 --- a/src/routes/milestones/list.spec.js +++ b/src/routes/milestones/list.spec.js @@ -186,18 +186,20 @@ describe('LIST timelines', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /timelines/{timelineId}/milestones', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/timelines') + .get('/v5/timelines') .expect(403, done); }); it('should return 403 for member with no accessible project', (done) => { request(server) - .get('/v4/timelines/1/milestones') + .get('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -206,34 +208,34 @@ describe('LIST timelines', () => { it('should return 404 for not-existed timeline', (done) => { request(server) - .get('/v4/timelines/11/milestones') + .get('/v5/timelines/11/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .expect(404, done); }); - it('should return 422 for invalid sort column', (done) => { + it('should return 400 for invalid sort column', (done) => { request(server) - .get('/v4/timelines/1/milestones?sort=id%20asc') + .get('/v5/timelines/1/milestones?sort=id%20asc') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for invalid sort order', (done) => { + it('should return 400 for invalid sort order', (done) => { request(server) - .get('/v4/timelines/1/milestones?sort=order%20invalid') + .get('/v5/timelines/1/milestones?sort=order%20invalid') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) - .expect(422, done); + .expect(400, done); }); it('should return 200 for admin', (done) => { request(server) - .get('/v4/timelines/1/milestones') + .get('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -251,7 +253,7 @@ describe('LIST timelines', () => { it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/timelines/1/milestones') + .get('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -266,7 +268,7 @@ describe('LIST timelines', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/timelines/1/milestones') + .get('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -281,7 +283,7 @@ describe('LIST timelines', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/timelines/1/milestones') + .get('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -295,7 +297,7 @@ describe('LIST timelines', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/timelines/1/milestones') + .get('/v5/timelines/1/milestones') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -309,7 +311,7 @@ describe('LIST timelines', () => { it('should return 200 with sort by order desc', (done) => { request(server) - .get('/v4/timelines/1/milestones?sort=order%20desc') + .get('/v5/timelines/1/milestones?sort=order%20desc') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/milestones/update.js b/src/routes/milestones/update.js index 98a5cdf1..6d8b053d 100644 --- a/src/routes/milestones/update.js +++ b/src/routes/milestones/update.js @@ -156,7 +156,7 @@ module.exports = [ if (entityToUpdate.completionDate && entityToUpdate.completionDate < milestone.startDate) { const apiErr = new Error('The milestone completionDate should be greater or equal than the startDate.'); - apiErr.status = 422; + apiErr.status = 400; return Promise.reject(apiErr); } diff --git a/src/routes/milestones/update.spec.js b/src/routes/milestones/update.spec.js index 9a694e73..06a1d060 100644 --- a/src/routes/milestones/update.spec.js +++ b/src/routes/milestones/update.spec.js @@ -257,7 +257,9 @@ describe('UPDATE Milestone', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('PATCH /timelines/{timelineId}/milestones/{milestoneId}', () => { const body = { @@ -287,14 +289,14 @@ describe('UPDATE Milestone', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .send(body) .expect(403, done); }); it('should return 403 for member who is not in the project', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -304,7 +306,7 @@ describe('UPDATE Milestone', () => { it('should return 404 for non-existed timeline', (done) => { request(server) - .patch('/v4/timelines/1234/milestones/1') + .patch('/v5/timelines/1234/milestones/1') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, @@ -314,7 +316,7 @@ describe('UPDATE Milestone', () => { it('should return 404 for deleted timeline', (done) => { request(server) - .patch('/v4/timelines/3/milestones/1') + .patch('/v5/timelines/3/milestones/1') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, @@ -324,7 +326,7 @@ describe('UPDATE Milestone', () => { it('should return 404 for non-existed Milestone', (done) => { request(server) - .patch('/v4/timelines/1/milestones/111') + .patch('/v5/timelines/1/milestones/111') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, @@ -334,7 +336,7 @@ describe('UPDATE Milestone', () => { it('should return 404 for deleted Milestone', (done) => { request(server) - .patch('/v4/timelines/1/milestones/5') + .patch('/v5/timelines/1/milestones/5') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, @@ -342,31 +344,31 @@ describe('UPDATE Milestone', () => { .expect(404, done); }); - it('should return 422 for invalid timelineId param', (done) => { + it('should return 400 for invalid timelineId param', (done) => { request(server) - .patch('/v4/timelines/0/milestones/1') + .patch('/v5/timelines/0/milestones/1') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for invalid milestoneId param', (done) => { + it('should return 400 for invalid milestoneId param', (done) => { request(server) - .patch('/v4/timelines/1/milestones/0') + .patch('/v5/timelines/1/milestones/0') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); it('should return 200 for missing name', (done) => { const partialBody = _.cloneDeep(body); delete partialBody.param.name; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -378,7 +380,7 @@ describe('UPDATE Milestone', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.type; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -390,7 +392,7 @@ describe('UPDATE Milestone', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.duration; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -402,7 +404,7 @@ describe('UPDATE Milestone', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.order; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -414,7 +416,7 @@ describe('UPDATE Milestone', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.plannedText; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -426,7 +428,7 @@ describe('UPDATE Milestone', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.blockedText; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -438,7 +440,7 @@ describe('UPDATE Milestone', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.activeText; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -450,7 +452,7 @@ describe('UPDATE Milestone', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.completedText; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -462,7 +464,7 @@ describe('UPDATE Milestone', () => { const partialBody = _.cloneDeep(body); delete partialBody.param.hidden; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -471,7 +473,7 @@ describe('UPDATE Milestone', () => { }); ['startDate', 'endDate'].forEach((field) => { - it(`should return 422 if ${field} is present in the payload`, (done) => { + it(`should return 400 if ${field} is present in the payload`, (done) => { const invalidBody = { param: _.assign({}, body.param, { [field]: '2018-07-01T00:00:00.000Z', @@ -479,19 +481,19 @@ describe('UPDATE Milestone', () => { }; request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); }); it('should return 200 for admin', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -536,7 +538,7 @@ describe('UPDATE Milestone', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -547,19 +549,19 @@ describe('UPDATE Milestone', () => { // Milestone 2: order 2 - 1 = 1 // Milestone 3: order 3 - 1 = 2 // Milestone 4: order 4 - 1 = 3 - models.Milestone.findById(1) + models.Milestone.findByPk(1) .then((milestone) => { milestone.order.should.be.eql(4); }) - .then(() => models.Milestone.findById(2)) + .then(() => models.Milestone.findByPk(2)) .then((milestone) => { milestone.order.should.be.eql(1); }) - .then(() => models.Milestone.findById(3)) + .then(() => models.Milestone.findByPk(3)) .then((milestone) => { milestone.order.should.be.eql(2); }) - .then(() => models.Milestone.findById(4)) + .then(() => models.Milestone.findByPk(4)) .then((milestone) => { milestone.order.should.be.eql(3); @@ -573,7 +575,7 @@ describe('UPDATE Milestone', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -584,19 +586,19 @@ describe('UPDATE Milestone', () => { // Milestone 2: order 2 // Milestone 3: order 3 // Milestone 4: order 4 - models.Milestone.findById(1) + models.Milestone.findByPk(1) .then((milestone) => { milestone.order.should.be.eql(5); }) - .then(() => models.Milestone.findById(2)) + .then(() => models.Milestone.findByPk(2)) .then((milestone) => { milestone.order.should.be.eql(2); }) - .then(() => models.Milestone.findById(3)) + .then(() => models.Milestone.findByPk(3)) .then((milestone) => { milestone.order.should.be.eql(3); }) - .then(() => models.Milestone.findById(4)) + .then(() => models.Milestone.findByPk(4)) .then((milestone) => { milestone.order.should.be.eql(4); @@ -610,7 +612,7 @@ describe('UPDATE Milestone', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/1/milestones/4') + .patch('/v5/timelines/1/milestones/4') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -621,19 +623,19 @@ describe('UPDATE Milestone', () => { // Milestone 2: order 3 // Milestone 3: order 4 // Milestone 4: order 2 - models.Milestone.findById(1) + models.Milestone.findByPk(1) .then((milestone) => { milestone.order.should.be.eql(1); }) - .then(() => models.Milestone.findById(2)) + .then(() => models.Milestone.findByPk(2)) .then((milestone) => { milestone.order.should.be.eql(3); }) - .then(() => models.Milestone.findById(3)) + .then(() => models.Milestone.findByPk(3)) .then((milestone) => { milestone.order.should.be.eql(4); }) - .then(() => models.Milestone.findById(4)) + .then(() => models.Milestone.findByPk(4)) .then((milestone) => { milestone.order.should.be.eql(2); @@ -647,7 +649,7 @@ describe('UPDATE Milestone', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/1/milestones/4') + .patch('/v5/timelines/1/milestones/4') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -658,19 +660,19 @@ describe('UPDATE Milestone', () => { // Milestone 2: order 2 // Milestone 3: order 3 // Milestone 4: order 0 - models.Milestone.findById(1) + models.Milestone.findByPk(1) .then((milestone) => { milestone.order.should.be.eql(1); }) - .then(() => models.Milestone.findById(2)) + .then(() => models.Milestone.findByPk(2)) .then((milestone) => { milestone.order.should.be.eql(2); }) - .then(() => models.Milestone.findById(3)) + .then(() => models.Milestone.findByPk(3)) .then((milestone) => { milestone.order.should.be.eql(3); }) - .then(() => models.Milestone.findById(4)) + .then(() => models.Milestone.findByPk(4)) .then((milestone) => { milestone.order.should.be.eql(0); @@ -684,7 +686,7 @@ describe('UPDATE Milestone', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/2/milestones/6') + .patch('/v5/timelines/2/milestones/6') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -692,7 +694,7 @@ describe('UPDATE Milestone', () => { .expect(200) .end(() => { // Milestone 6: order 0 - models.Milestone.findById(6) + models.Milestone.findByPk(6) .then((milestone) => { milestone.order.should.be.eql(0); @@ -746,7 +748,7 @@ describe('UPDATE Milestone', () => { ]) .then(() => { request(server) - .patch('/v4/timelines/2/milestones/8') + .patch('/v5/timelines/2/milestones/8') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -756,15 +758,15 @@ describe('UPDATE Milestone', () => { // Milestone 6: order 1 => 1 // Milestone 7: order 3 => 3 // Milestone 8: order 4 => 2 - models.Milestone.findById(6) + models.Milestone.findByPk(6) .then((milestone) => { milestone.order.should.be.eql(1); }) - .then(() => models.Milestone.findById(7)) + .then(() => models.Milestone.findByPk(7)) .then((milestone) => { milestone.order.should.be.eql(3); }) - .then(() => models.Milestone.findById(8)) + .then(() => models.Milestone.findByPk(8)) .then((milestone) => { milestone.order.should.be.eql(2); @@ -819,7 +821,7 @@ describe('UPDATE Milestone', () => { ]) .then(() => { request(server) - .patch('/v4/timelines/2/milestones/8') + .patch('/v5/timelines/2/milestones/8') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -829,15 +831,15 @@ describe('UPDATE Milestone', () => { // Milestone 6: order 1 => 1 // Milestone 7: order 2 => 3 // Milestone 8: order 4 => 2 - models.Milestone.findById(6) + models.Milestone.findByPk(6) .then((milestone) => { milestone.order.should.be.eql(1); }) - .then(() => models.Milestone.findById(7)) + .then(() => models.Milestone.findByPk(7)) .then((milestone) => { milestone.order.should.be.eql(3); }) - .then(() => models.Milestone.findById(8)) + .then(() => models.Milestone.findByPk(8)) .then((milestone) => { milestone.order.should.be.eql(2); @@ -856,7 +858,7 @@ describe('UPDATE Milestone', () => { .milliseconds(0); request(server) - .patch('/v4/timelines/1/milestones/2') + .patch('/v5/timelines/1/milestones/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -870,7 +872,7 @@ describe('UPDATE Milestone', () => { // endDate: null to today + 5 (5 = 3 + duration - 1) // Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to today + 6 // endDate: null to today + 8 (2 = 6 + duration - 1) - models.Milestone.findById(2) + models.Milestone.findByPk(2) .then((milestone) => { should.exist(milestone.actualStartDate); moment.utc(milestone.actualStartDate).diff(today, 'days').should.be.eql(0); @@ -880,7 +882,7 @@ describe('UPDATE Milestone', () => { // end date of the updated milestone should change, as delayed start caused scheduled to be delayed moment.utc(milestone.endDate).diff(today, 'days').should.be.eql(0); milestone.status.should.be.eql(MILESTONE_STATUS.ACTIVE); - return models.Milestone.findById(3); + return models.Milestone.findByPk(3); }) .then((milestone) => { today.add('days', 1); // should have start date next to previous one's end date @@ -888,7 +890,7 @@ describe('UPDATE Milestone', () => { should.not.exist(milestone.actualStartDate); today.add('days', milestone.duration - 1); moment.utc(milestone.endDate).diff(today, 'days').should.be.eql(0); - return models.Milestone.findById(4); + return models.Milestone.findByPk(4); }) .then((milestone) => { today.add('days', 1); // should have start date next to previous one's end date @@ -910,7 +912,7 @@ describe('UPDATE Milestone', () => { .milliseconds(0); request(server) - .patch('/v4/timelines/1/milestones/2') + .patch('/v5/timelines/1/milestones/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -923,7 +925,7 @@ describe('UPDATE Milestone', () => { // endDate: null to '2018-05-21T00:00:00.000Z' // Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z' // endDate: null to '2018-05-24T00:00:00.000Z' - models.Milestone.findById(3) + models.Milestone.findByPk(3) .then((milestone) => { milestone.startDate.should.be.eql(new Date('2018-05-19T00:00:00.000Z')); should.exist(milestone.actualStartDate); @@ -931,7 +933,7 @@ describe('UPDATE Milestone', () => { // milestone.actualStartDate.should.be.eql(today); milestone.endDate.should.be.eql(new Date('2018-05-21T00:00:00.000Z')); milestone.status.should.be.eql(MILESTONE_STATUS.ACTIVE); - return models.Milestone.findById(4); + return models.Milestone.findByPk(4); }) .then((milestone) => { milestone.startDate.should.be.eql(new Date('2018-05-22T00:00:00.000Z')); @@ -949,7 +951,7 @@ describe('UPDATE Milestone', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/1/milestones/2') + .patch('/v5/timelines/1/milestones/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -963,7 +965,7 @@ describe('UPDATE Milestone', () => { // Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z' // BELOW will be the new timeline's endDate // endDate: null to '2018-05-24T00:00:00.000Z' - models.Timeline.findById(1) + models.Timeline.findByPk(1) .then((timeline) => { // timeline start shouldn't change timeline.startDate.should.be.eql(new Date('2018-05-02T00:00:00.000Z')); @@ -983,7 +985,7 @@ describe('UPDATE Milestone', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/1/milestones/2') + .patch('/v5/timelines/1/milestones/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -994,11 +996,11 @@ describe('UPDATE Milestone', () => { // endDate: null to '2018-05-21T00:00:00.000Z' // Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z' // endDate: null to '2018-05-24T00:00:00.000Z' - models.Milestone.findById(3) + models.Milestone.findByPk(3) .then((milestone) => { milestone.startDate.should.be.eql(new Date('2018-05-19T00:00:00.000Z')); milestone.endDate.should.be.eql(new Date('2018-05-21T00:00:00.000Z')); - return models.Milestone.findById(4); + return models.Milestone.findByPk(4); }) .then((milestone) => { milestone.startDate.should.be.eql(new Date('2018-05-22T00:00:00.000Z')); @@ -1015,7 +1017,7 @@ describe('UPDATE Milestone', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/1/milestones/2') + .patch('/v5/timelines/1/milestones/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -1027,7 +1029,7 @@ describe('UPDATE Milestone', () => { // Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z' // BELOW will be the new timeline's endDate // endDate: null to '2018-05-24T00:00:00.000Z' - models.Timeline.findById(1) + models.Timeline.findByPk(1) .then((timeline) => { // timeline start shouldn't change timeline.startDate.should.be.eql(new Date('2018-05-02T00:00:00.000Z')); @@ -1043,7 +1045,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for connect admin', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -1054,7 +1056,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for connect manager', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -1065,7 +1067,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for copilot', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -1076,7 +1078,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for member', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -1104,7 +1106,7 @@ describe('UPDATE Milestone', () => { it('should send message BUS_API_EVENT.MILESTONE_WAITING_CUSTOMER when milestone duration updated', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -1125,7 +1127,7 @@ describe('UPDATE Milestone', () => { // 5 milestones in total, so it would trigger 5 events // 4 MILESTONE_UPDATED events are for 4 non deleted milestones // 1 TIMELINE_ADJUSTED event, because timeline's end date updated - createEventSpy.callCount.should.be.eql(2); + createEventSpy.callCount.should.be.eql(3); createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ projectId: 1, projectName: 'test1', @@ -1133,7 +1135,7 @@ describe('UPDATE Milestone', () => { userId: 40051332, initiatorUserId: 40051332, })).should.be.true; - createEventSpy.lastCall.calledWith(BUS_API_EVENT.MILESTONE_WAITING_CUSTOMER).should.be.true; + // createEventSpy.lastCall.calledWith(BUS_API_EVENT.MILESTONE_WAITING_CUSTOMER).should.be.true; done(); }); } @@ -1142,7 +1144,7 @@ describe('UPDATE Milestone', () => { xit('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when milestone duration updated', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -1177,7 +1179,7 @@ describe('UPDATE Milestone', () => { xit('should send message BUS_API_EVENT.MILESTONE_UPDATED when milestone status updated', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -1208,7 +1210,7 @@ describe('UPDATE Milestone', () => { it('should ONLY send message BUS_API_EVENT.MILESTONE_UPDATED when milestone order updated', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -1223,7 +1225,7 @@ describe('UPDATE Milestone', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; + createEventSpy.calledTwice.should.be.true; createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ projectId: 1, projectName: 'test1', @@ -1239,7 +1241,7 @@ describe('UPDATE Milestone', () => { it('should ONLY send message BUS_API_EVENT.MILESTONE_UPDATED when milestone plannedText updated', (done) => { request(server) - .patch('/v4/timelines/1/milestones/1') + .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -1254,7 +1256,7 @@ describe('UPDATE Milestone', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; + createEventSpy.calledTwice.should.be.true; createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ projectId: 1, projectName: 'test1', diff --git a/src/routes/orgConfig/create.js b/src/routes/orgConfig/create.js index 6b53d6d2..41e4968f 100644 --- a/src/routes/orgConfig/create.js +++ b/src/routes/orgConfig/create.js @@ -5,53 +5,57 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; const schema = { - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - orgId: Joi.string().max(45).required(), - configName: Joi.string().max(45).required(), - configValue: Joi.string().max(512), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + + body: Joi.object().keys({ + id: Joi.any().strip(), + orgId: Joi.string().max(45).required(), + configName: Joi.string().max(45).required(), + configValue: Joi.string().max(512), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), + }; module.exports = [ validate(schema), permissions('orgConfig.create'), (req, res, next) => { - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { createdBy: req.authUser.userId, updatedBy: req.authUser.userId, }); // Check if duplicated key - return models.OrgConfig.findOne({ where: { orgId: req.body.param.orgId, configName: req.body.param.configName } }) + return models.OrgConfig.findOne({ where: { orgId: req.body.orgId, configName: req.body.configName } }) .then((existing) => { if (existing) { - const apiErr = new Error(`Organization config exists for orgId ${req.body.param.orgId} - and configName ${req.body.param.configName}`); - apiErr.status = 422; + const apiErr = new Error(`Organization config exists for orgId ${req.body.orgId} + and configName ${req.body.configName}`); + apiErr.status = 400; return Promise.reject(apiErr); } // Create return models.OrgConfig.create(entity); }).then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.ORG_CONFIG, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next); }, diff --git a/src/routes/orgConfig/create.spec.js b/src/routes/orgConfig/create.spec.js index c13a7521..b5113015 100644 --- a/src/routes/orgConfig/create.spec.js +++ b/src/routes/orgConfig/create.spec.js @@ -12,36 +12,37 @@ import models from '../../models'; const should = chai.should(); describe('CREATE organization config', () => { - beforeEach(() => testUtil.clearDb() - .then(() => models.OrgConfig.create({ - orgId: 'ORG1', - configName: 'project_catefory_url', - configValue: 'http://localhost/url', - createdBy: 1, - updatedBy: 1, - })).then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.OrgConfig.create({ + orgId: 'ORG1', + configName: 'project_catefory_url', + configValue: 'http://localhost/url', + createdBy: 1, + updatedBy: 1, + }).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /orgConfig', () => { const body = { - param: { - orgId: 'ORG2', - configName: 'project_catefory_url', - configValue: 'http://localhost/url', - }, + orgId: 'ORG2', + configName: 'project_catefory_url', + configValue: 'http://localhost/url', }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/orgConfig') + .post('/v5/projects/metadata/orgConfig') .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/orgConfig') + .post('/v5/projects/metadata/orgConfig') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -51,7 +52,7 @@ describe('CREATE organization config', () => { it('should return 403 for copilot', (done) => { request(server) - .post('/v4/projects/metadata/orgConfig') + .post('/v5/projects/metadata/orgConfig') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -61,7 +62,7 @@ describe('CREATE organization config', () => { it('should return 403 for manager', (done) => { request(server) - .post('/v4/projects/metadata/orgConfig') + .post('/v5/projects/metadata/orgConfig') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -69,52 +70,52 @@ describe('CREATE organization config', () => { .expect(403, done); }); - it('should return 422 for missing orgId', (done) => { + it('should return 400 for missing orgId', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.orgId; + delete invalidBody.orgId; request(server) - .post('/v4/projects/metadata/orgConfig') + .post('/v5/projects/metadata/orgConfig') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing configName', (done) => { + it('should return 400 for missing configName', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.configName; + delete invalidBody.configName; request(server) - .post('/v4/projects/metadata/orgConfig') + .post('/v5/projects/metadata/orgConfig') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for duplicated orgId and configName', (done) => { + it('should return 400 for duplicated orgId and configName', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.orgId = 'ORG1'; - invalidBody.param.configName = 'project_catefory_url'; + invalidBody.orgId = 'ORG1'; + invalidBody.configName = 'project_catefory_url'; request(server) - .post('/v4/projects/metadata/orgConfig') + .post('/v5/projects/metadata/orgConfig') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/orgConfig') + .post('/v5/projects/metadata/orgConfig') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -122,10 +123,10 @@ describe('CREATE organization config', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; - resJson.orgId.should.be.eql(body.param.orgId); - resJson.configName.should.be.eql(body.param.configName); - resJson.configValue.should.be.eql(body.param.configValue); + const resJson = res.body; + resJson.orgId.should.be.eql(body.orgId); + resJson.configName.should.be.eql(body.configName); + resJson.configValue.should.be.eql(body.configValue); resJson.createdBy.should.be.eql(40051333); // admin should.exist(resJson.createdAt); @@ -140,7 +141,7 @@ describe('CREATE organization config', () => { it('should return 201 for connect admin', (done) => { request(server) - .post('/v4/projects/metadata/orgConfig') + .post('/v5/projects/metadata/orgConfig') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -148,10 +149,10 @@ describe('CREATE organization config', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; - resJson.orgId.should.be.eql(body.param.orgId); - resJson.configName.should.be.eql(body.param.configName); - resJson.configValue.should.be.eql(body.param.configValue); + const resJson = res.body; + resJson.orgId.should.be.eql(body.orgId); + resJson.configName.should.be.eql(body.configName); + resJson.configValue.should.be.eql(body.configValue); resJson.createdBy.should.be.eql(40051336); // connect admin resJson.updatedBy.should.be.eql(40051336); // connect admin done(); diff --git a/src/routes/orgConfig/delete.js b/src/routes/orgConfig/delete.js index d33e7608..91c59bb4 100644 --- a/src/routes/orgConfig/delete.js +++ b/src/routes/orgConfig/delete.js @@ -2,8 +2,11 @@ * API to delete a organization config */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; +import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; @@ -19,7 +22,7 @@ module.exports = [ permissions('orgConfig.delete'), (req, res, next) => models.sequelize.transaction(() => - models.OrgConfig.findById(req.params.id) + models.OrgConfig.findByPk(req.params.id) .then((entity) => { if (!entity) { const apiErr = new Error(`Organization config not found for id ${req.params.id}`); @@ -30,7 +33,11 @@ module.exports = [ return entity.update({ deletedBy: req.authUser.userId }); }) .then(entity => entity.destroy())) - .then(() => { + .then((entity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.ORG_CONFIG, + _.pick(entity.toJSON(), 'id')); res.status(204).end(); }) .catch(next), diff --git a/src/routes/orgConfig/delete.spec.js b/src/routes/orgConfig/delete.spec.js index 0bc6aefc..e4a234b5 100644 --- a/src/routes/orgConfig/delete.spec.js +++ b/src/routes/orgConfig/delete.spec.js @@ -24,7 +24,7 @@ const expectAfterDelete = (id, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/projects/metadata/orgConfig/${id}`) + .get(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -36,28 +36,31 @@ const expectAfterDelete = (id, err, next) => { describe('DELETE organization config', () => { const id = 1; - beforeEach(() => testUtil.clearDb() - .then(() => models.OrgConfig.create({ - id: 1, - orgId: 'ORG1', - configName: 'project_category_url', - configValue: '/projects/1', - createdBy: 1, - updatedBy: 1, - })).then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.OrgConfig.create({ + id: 1, + orgId: 'ORG1', + configName: 'project_category_url', + configValue: '/projects/1', + createdBy: 1, + updatedBy: 1, + }).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /orgConfig/{id}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete(`/v4/projects/metadata/orgConfig/${id}`) + .delete(`/v5/projects/metadata/orgConfig/${id}`) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete(`/v4/projects/metadata/orgConfig/${id}`) + .delete(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -66,7 +69,7 @@ describe('DELETE organization config', () => { it('should return 403 for copilot', (done) => { request(server) - .delete(`/v4/projects/metadata/orgConfig/${id}`) + .delete(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -75,7 +78,7 @@ describe('DELETE organization config', () => { it('should return 403 for manager', (done) => { request(server) - .delete(`/v4/projects/metadata/orgConfig/${id}`) + .delete(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -84,7 +87,7 @@ describe('DELETE organization config', () => { it('should return 404 for non-existed config', (done) => { request(server) - .delete('/v4/projects/metadata/orgConfig/not_existed') + .delete('/v5/projects/metadata/orgConfig/not_existed') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -95,7 +98,7 @@ describe('DELETE organization config', () => { models.OrgConfig.destroy({ where: { id } }) .then(() => { request(server) - .delete(`/v4/projects/metadata/orgConfig/${id}`) + .delete(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -105,7 +108,7 @@ describe('DELETE organization config', () => { it('should return 204, for admin, if config was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/orgConfig/${id}`) + .delete(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -115,7 +118,7 @@ describe('DELETE organization config', () => { it('should return 204, for connect admin, if config was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/orgConfig/${id}`) + .delete(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/orgConfig/get.js b/src/routes/orgConfig/get.js index 5c14779b..1ad68306 100644 --- a/src/routes/orgConfig/get.js +++ b/src/routes/orgConfig/get.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import models from '../../models'; +import util from '../../util'; const permissions = tcMiddleware.permissions; @@ -18,22 +18,46 @@ const schema = { module.exports = [ validate(schema), permissions('orgConfig.view'), - (req, res, next) => models.OrgConfig.findOne({ - where: { - id: req.params.id, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((orgConfig) => { - // Not found - if (!orgConfig) { - const apiErr = new Error(`Organization config not found for id ${req.params.id}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => { + util.fetchByIdFromES('orgConfigs', { + query: { + nested: { + path: 'orgConfigs', + query: { + match: { 'orgConfigs.id': req.params.id }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No orgConfig found in ES'); + models.OrgConfig.findOne({ + where: { + id: req.params.id, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((orgConfig) => { + // Not found + if (!orgConfig) { + const apiErr = new Error(`Organization config not found for id ${req.params.id}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, orgConfig)); - return Promise.resolve(); + res.json(orgConfig); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('orgConfigs found in ES'); + res.json(data[0].inner_hits.orgConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } }) - .catch(next), + .catch(next); + }, + + ]; diff --git a/src/routes/orgConfig/get.spec.js b/src/routes/orgConfig/get.spec.js index 5bafd4c0..eddd8b0a 100644 --- a/src/routes/orgConfig/get.spec.js +++ b/src/routes/orgConfig/get.spec.js @@ -22,16 +22,18 @@ describe('GET organization config', () => { const id = config.id; - beforeEach(() => testUtil.clearDb() - .then(() => models.OrgConfig.create(config)) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.OrgConfig.create(config).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /orgConfig/{id}', () => { it('should return 404 for non-existed config', (done) => { request(server) - .get('/v4/projects/metadata/orgConfig/1234') + .get('/v5/projects/metadata/orgConfig/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -42,7 +44,7 @@ describe('GET organization config', () => { models.OrgConfig.destroy({ where: { id } }) .then(() => { request(server) - .get(`/v4/projects/metadata/orgConfig/${id}`) + .get(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -52,13 +54,13 @@ describe('GET organization config', () => { it('should return 200 for admin', (done) => { request(server) - .get(`/v4/projects/metadata/orgConfig/${id}`) + .get(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(config.id); resJson.orgId.should.be.eql(config.orgId); resJson.configName.should.be.eql(config.configName); @@ -76,13 +78,13 @@ describe('GET organization config', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get(`/v4/projects/metadata/orgConfig/${id}`) + .get(`/v5/projects/metadata/orgConfig/${id}`) .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get(`/v4/projects/metadata/orgConfig/${id}`) + .get(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -92,7 +94,7 @@ describe('GET organization config', () => { it('should return 200 for connect manager', (done) => { request(server) - .get(`/v4/projects/metadata/orgConfig/${id}`) + .get(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -102,7 +104,7 @@ describe('GET organization config', () => { it('should return 200 for member', (done) => { request(server) - .get(`/v4/projects/metadata/orgConfig/${id}`) + .get(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -111,7 +113,7 @@ describe('GET organization config', () => { it('should return 200 for copilot', (done) => { request(server) - .get(`/v4/projects/metadata/orgConfig/${id}`) + .get(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/orgConfig/list.js b/src/routes/orgConfig/list.js index 0c8222f0..bb99e4d0 100644 --- a/src/routes/orgConfig/list.js +++ b/src/routes/orgConfig/list.js @@ -19,26 +19,37 @@ module.exports = [ validate(schema), permissions('orgConfig.view'), (req, res, next) => { - // handle filters - const filters = util.parseQueryFilter(req.query.filter); - // Throw error if orgId is not present in filter - if (!filters.orgId) { - return next(util.buildApiError('Missing filter orgId', 422)); - } - if (!util.isValidFilter(filters, ['orgId', 'configName'])) { - return util.handleError('Invalid filters', null, req, next); - } - req.log.debug(filters); - // Get all organization config - const where = filters || {}; - return models.OrgConfig.findAll({ - where, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((orgConfigs) => { - res.json(util.wrapResponse(req.id, orgConfigs)); - }) - .catch(next); + util.fetchFromES('orgConfigs') + .then((data) => { + // handle filters + const filters = util.parseQueryFilter(req.query.filter); + // Throw error if orgId is not present in filter + if (!filters.orgId) { + next(util.buildApiError('Missing filter orgId', 400)); + } + if (!util.isValidFilter(filters, ['orgId', 'configName'])) { + util.handleError('Invalid filters', null, req, next); + } + req.log.debug(filters); + + if (data.orgConfigs.length === 0) { + req.log.debug('No orgConfig found in ES'); + + // Get all organization config + const where = filters || {}; + models.OrgConfig.findAll({ + where, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((orgConfigs) => { + res.json(orgConfigs); + }) + .catch(next); + } else { + req.log.debug('orgConfigs found in ES'); + res.json(data.orgConfigs); + } + }); }, ]; diff --git a/src/routes/orgConfig/list.spec.js b/src/routes/orgConfig/list.spec.js index 060a1a79..14ae71c1 100644 --- a/src/routes/orgConfig/list.spec.js +++ b/src/routes/orgConfig/list.spec.js @@ -11,7 +11,7 @@ import testUtil from '../../tests/util'; const should = chai.should(); describe('LIST organization config', () => { - const orgConfigPath = '/v4/projects/metadata/orgConfig'; + const orgConfigPath = '/v5/projects/metadata/orgConfig'; const configs = [ { id: 1, @@ -31,10 +31,13 @@ describe('LIST organization config', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.OrgConfig.bulkCreate(configs)), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.OrgConfig.bulkCreate(configs).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /orgConfig', () => { it('should return 200 for admin with filter', (done) => { @@ -47,7 +50,7 @@ describe('LIST organization config', () => { .end((err, res) => { const config = configs[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(1); resJson[0].id.should.be.eql(config.id); resJson[0].orgId.should.be.eql(config.orgId); @@ -107,22 +110,22 @@ describe('LIST organization config', () => { .expect(200, done); }); - it('should return 422 without filter query param', (done) => { + it('should return 400 without filter query param', (done) => { request(server) .get(`${orgConfigPath}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); - it('should return 422 with filter query param but without orgId defined', (done) => { + it('should return 400 with filter query param but without orgId defined', (done) => { request(server) .get(`${orgConfigPath}?filter=configName=${configs[0].configName}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); }); }); diff --git a/src/routes/orgConfig/update.js b/src/routes/orgConfig/update.js index 7bc2b52d..734a26d3 100644 --- a/src/routes/orgConfig/update.js +++ b/src/routes/orgConfig/update.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import util from '../../util'; import models from '../../models'; @@ -14,27 +15,25 @@ const schema = { params: { id: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - orgId: Joi.string().max(45).optional(), - configName: Joi.string().max(45).optional(), - configValue: Joi.string().max(512).optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + id: Joi.any().strip(), + orgId: Joi.string().max(45).optional(), + configName: Joi.string().max(45).optional(), + configValue: Joi.string().max(512).optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ validate(schema), permissions('orgConfig.edit'), (req, res, next) => { - const entityToUpdate = _.assign(req.body.param, { + const entityToUpdate = _.assign(req.body, { updatedBy: req.authUser.userId, }); @@ -55,7 +54,11 @@ module.exports = [ return orgConfig.update(entityToUpdate); }) .then((orgConfig) => { - res.json(util.wrapResponse(req.id, orgConfig)); + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, + RESOURCES.ORG_CONFIG, + entityToUpdate); + res.json(orgConfig); return Promise.resolve(); }) .catch(next); diff --git a/src/routes/orgConfig/update.spec.js b/src/routes/orgConfig/update.spec.js index e1f18672..4921c586 100644 --- a/src/routes/orgConfig/update.spec.js +++ b/src/routes/orgConfig/update.spec.js @@ -22,32 +22,32 @@ describe('UPDATE organization config', () => { }; const id = config.id; - beforeEach(() => testUtil.clearDb() - .then(() => models.OrgConfig.create(config)) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.OrgConfig.create(config).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('PATCH /orgConfig/{id}', () => { const body = { - param: { - id: 1, - orgId: 'ORG2', - configName: 'project_category_url_update', - configValue: '/projects/2', - }, + id: 1, + orgId: 'ORG2', + configName: 'project_category_url_update', + configValue: '/projects/2', }; it('should return 403 if user is not authenticated', (done) => { request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -57,7 +57,7 @@ describe('UPDATE organization config', () => { it('should return 403 for copilot', (done) => { request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, @@ -67,7 +67,7 @@ describe('UPDATE organization config', () => { it('should return 403 for manager', (done) => { request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, @@ -77,7 +77,7 @@ describe('UPDATE organization config', () => { it('should return 404 for non-existed config', (done) => { request(server) - .patch('/v4/projects/metadata/orgConfig/1234') + .patch('/v5/projects/metadata/orgConfig/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -89,7 +89,7 @@ describe('UPDATE organization config', () => { models.OrgConfig.destroy({ where: { id } }) .then(() => { request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -100,21 +100,21 @@ describe('UPDATE organization config', () => { it('should return 200 for admin configValue updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.orgId; - delete partialBody.param.configName; + delete partialBody.orgId; + delete partialBody.configName; request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(id); resJson.orgId.should.be.eql(config.orgId); resJson.configName.should.be.eql(config.configName); - resJson.configValue.should.be.eql(partialBody.param.configValue); + resJson.configValue.should.be.eql(partialBody.configValue); resJson.createdBy.should.be.eql(config.createdBy); resJson.createdBy.should.be.eql(config.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051333); // admin @@ -128,19 +128,19 @@ describe('UPDATE organization config', () => { it('should return 200 for admin orgId updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.configName; - delete partialBody.param.configValue; + delete partialBody.configName; + delete partialBody.configValue; request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(id); - resJson.orgId.should.be.eql(partialBody.param.orgId); + resJson.orgId.should.be.eql(partialBody.orgId); resJson.configName.should.be.eql(config.configName); resJson.configValue.should.be.eql(config.configValue); resJson.createdBy.should.be.eql(config.createdBy); @@ -156,20 +156,20 @@ describe('UPDATE organization config', () => { it('should return 200 for admin configName updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.orgId; - delete partialBody.param.configValue; + delete partialBody.orgId; + delete partialBody.configValue; request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(id); resJson.orgId.should.be.eql(config.orgId); - resJson.configName.should.be.eql(partialBody.param.configName); + resJson.configName.should.be.eql(partialBody.configName); resJson.configValue.should.be.eql(config.configValue); resJson.createdBy.should.be.eql(config.createdBy); resJson.createdBy.should.be.eql(config.createdBy); // should not update createdAt @@ -184,18 +184,18 @@ describe('UPDATE organization config', () => { it('should return 200 for admin all fields updated', (done) => { request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(id); - resJson.orgId.should.be.eql(body.param.orgId); - resJson.configName.should.be.eql(body.param.configName); - resJson.configValue.should.be.eql(body.param.configValue); + resJson.orgId.should.be.eql(body.orgId); + resJson.configName.should.be.eql(body.configName); + resJson.configValue.should.be.eql(body.configValue); resJson.createdBy.should.be.eql(config.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051333); // admin should.exist(resJson.updatedAt); @@ -208,18 +208,18 @@ describe('UPDATE organization config', () => { it('should return 200 for connect admin', (done) => { request(server) - .patch(`/v4/projects/metadata/orgConfig/${id}`) + .patch(`/v5/projects/metadata/orgConfig/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(id); - resJson.orgId.should.be.eql(body.param.orgId); - resJson.configName.should.be.eql(body.param.configName); - resJson.configValue.should.be.eql(body.param.configValue); + resJson.orgId.should.be.eql(body.orgId); + resJson.configName.should.be.eql(body.configName); + resJson.configValue.should.be.eql(body.configValue); resJson.createdBy.should.be.eql(config.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051336); // connect admin done(); diff --git a/src/routes/phaseProducts/create.spec.js b/src/routes/phaseProducts/create.spec.js index 8f81a28b..e28dc661 100644 --- a/src/routes/phaseProducts/create.spec.js +++ b/src/routes/phaseProducts/create.spec.js @@ -101,7 +101,7 @@ describe('Phase Products', () => { describe('POST /projects/{projectId}/phases/{phaseId}/products', () => { it('should return 403 if user does not have permissions (non team member)', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -112,7 +112,7 @@ describe('Phase Products', () => { it('should return 403 if user does not have permissions (customer)', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -121,61 +121,61 @@ describe('Phase Products', () => { .expect(403, done); }); - it('should return 422 when name not provided', (done) => { + it('should return 400 when name not provided', (done) => { const reqBody = _.cloneDeep(body); delete reqBody.name; request(server) - .post(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ param: reqBody }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 when type not provided', (done) => { + it('should return 400 when type not provided', (done) => { const reqBody = _.cloneDeep(body); delete reqBody.type; request(server) - .post(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ param: reqBody }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 when estimatedPrice is negative', (done) => { + it('should return 400 when estimatedPrice is negative', (done) => { const reqBody = _.cloneDeep(body); reqBody.estimatedPrice = -20; request(server) - .post(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ param: reqBody }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 when actualPrice is negative', (done) => { + it('should return 400 when actualPrice is negative', (done) => { const reqBody = _.cloneDeep(body); reqBody.actualPrice = -20; request(server) - .post(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ param: reqBody }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 404 when project is not found', (done) => { request(server) - .post(`/v4/projects/99999/phases/${phaseId}/products`) + .post(`/v5/projects/99999/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -186,7 +186,7 @@ describe('Phase Products', () => { it('should return 404 when project phase is not found', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/99999/products`) + .post(`/v5/projects/${projectId}/phases/99999/products`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -197,7 +197,7 @@ describe('Phase Products', () => { it('should return 201 if payload is valid', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -239,7 +239,7 @@ describe('Phase Products', () => { it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when product phase created', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phaseProducts/delete.spec.js b/src/routes/phaseProducts/delete.spec.js index 69942fa6..ea5da981 100644 --- a/src/routes/phaseProducts/delete.spec.js +++ b/src/routes/phaseProducts/delete.spec.js @@ -27,7 +27,7 @@ const expectAfterDelete = (projectId, phaseId, id, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products/${id}`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -134,7 +134,7 @@ describe('Phase Products', () => { describe('DELETE /projects/{id}/phases/{phaseId}/products/{productId}', () => { it('should return 403 when user have no permission (non team member)', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -144,7 +144,7 @@ describe('Phase Products', () => { it('should return 403 when user have no permission (customer)', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -154,7 +154,7 @@ describe('Phase Products', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .delete(`/v4/projects/999/phases/${phaseId}/products/${productId}`) + .delete(`/v5/projects/999/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -164,7 +164,7 @@ describe('Phase Products', () => { it('should return 404 when no phase with specific phaseId', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/99999/products/${productId}`) + .delete(`/v5/projects/${projectId}/phases/99999/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -174,7 +174,7 @@ describe('Phase Products', () => { it('should return 404 when no product with specific productId', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/99999`) + .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -184,7 +184,7 @@ describe('Phase Products', () => { it('should return 204 when user have project permission', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -211,7 +211,7 @@ describe('Phase Products', () => { it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when product phase removed', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phaseProducts/get.spec.js b/src/routes/phaseProducts/get.spec.js index a71d8a7f..5ab6b4fe 100644 --- a/src/routes/phaseProducts/get.spec.js +++ b/src/routes/phaseProducts/get.spec.js @@ -107,7 +107,7 @@ describe('Phase Products', () => { describe('GET /projects/{id}/phases/{phaseId}/products/{productId}', () => { it('should return 403 when user have no permission (non team member)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -117,7 +117,7 @@ describe('Phase Products', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .get(`/v4/projects/999/phases/${phaseId}/products/${productId}`) + .get(`/v5/projects/999/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -127,7 +127,7 @@ describe('Phase Products', () => { it('should return 404 when no phase with specific phaseId', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/99999/products/${productId}`) + .get(`/v5/projects/${projectId}/phases/99999/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -137,7 +137,7 @@ describe('Phase Products', () => { it('should return 404 when no product with specific productId', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products/99999`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -147,7 +147,7 @@ describe('Phase Products', () => { it('should return 1 phase when user have project permission (customer)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -171,7 +171,7 @@ describe('Phase Products', () => { it('should return 1 phase when user have project permission (copilot)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phaseProducts/list-db.spec.js b/src/routes/phaseProducts/list-db.spec.js index eb32119c..e78f4b23 100644 --- a/src/routes/phaseProducts/list-db.spec.js +++ b/src/routes/phaseProducts/list-db.spec.js @@ -112,7 +112,7 @@ describe('Phase Products', () => { describe('GET /projects/{id}/phases/{phaseId}/products/db', () => { it('should return 403 when user have no permission (non team member)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products/db`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products/db`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -123,7 +123,7 @@ describe('Phase Products', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .get(`/v4/projects/999/phases/${phaseId}/products/db`) + .get(`/v5/projects/999/phases/${phaseId}/products/db`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -134,7 +134,7 @@ describe('Phase Products', () => { it('should return 404 when no phase with specific phaseId', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/99999/products/db`) + .get(`/v5/projects/${projectId}/phases/99999/products/db`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -145,7 +145,7 @@ describe('Phase Products', () => { it('should return 1 phase when user have project permission (customer)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products/db`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products/db`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -166,7 +166,7 @@ describe('Phase Products', () => { it('should return 1 phase when user have project permission (copilot)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products/db`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products/db`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phaseProducts/list.spec.js b/src/routes/phaseProducts/list.spec.js index 8dcd08d2..e670b65e 100644 --- a/src/routes/phaseProducts/list.spec.js +++ b/src/routes/phaseProducts/list.spec.js @@ -128,7 +128,7 @@ describe('Phase Products', () => { describe('GET /projects/{id}/phases/{phaseId}/products', () => { it('should return 403 when user have no permission (non team member)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -139,7 +139,7 @@ describe('Phase Products', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .get(`/v4/projects/999/phases/${phaseId}/products`) + .get(`/v5/projects/999/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -150,7 +150,7 @@ describe('Phase Products', () => { it('should return 404 when no phase with specific phaseId', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/99999/products`) + .get(`/v5/projects/${projectId}/phases/99999/products`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -161,7 +161,7 @@ describe('Phase Products', () => { it('should return 1 phase when user have project permission (customer)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -182,7 +182,7 @@ describe('Phase Products', () => { it('should return 1 phase when user have project permission (copilot)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}/products`) + .get(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phaseProducts/update.spec.js b/src/routes/phaseProducts/update.spec.js index 3c35871b..fabbe819 100644 --- a/src/routes/phaseProducts/update.spec.js +++ b/src/routes/phaseProducts/update.spec.js @@ -120,7 +120,7 @@ describe('Phase Products', () => { describe('PATCH /projects/{id}/phases/{phaseId}/products/{productId}', () => { it('should return 403 when user have no permission (non team member)', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -131,7 +131,7 @@ describe('Phase Products', () => { it('should return 403 when user have no permission (customer)', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -142,7 +142,7 @@ describe('Phase Products', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .patch(`/v4/projects/999/phases/${phaseId}/products/${productId}`) + .patch(`/v5/projects/999/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -153,7 +153,7 @@ describe('Phase Products', () => { it('should return 404 when no phase with specific phaseId', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/99999/products/${productId}`) + .patch(`/v5/projects/${projectId}/phases/99999/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -164,7 +164,7 @@ describe('Phase Products', () => { it('should return 404 when no product with specific productId', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/99999`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -173,9 +173,9 @@ describe('Phase Products', () => { .expect(404, done); }); - it('should return 422 when parameters are invalid', (done) => { + it('should return 400 when parameters are invalid', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/99999`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -185,13 +185,13 @@ describe('Phase Products', () => { }, }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return updated product when user have permission and parameters are valid', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -233,7 +233,7 @@ describe('Phase Products', () => { it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when name updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -265,7 +265,7 @@ describe('Phase Products', () => { it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when estimatedPrice updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -297,7 +297,7 @@ describe('Phase Products', () => { it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when actualPrice updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -329,7 +329,7 @@ describe('Phase Products', () => { it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -362,7 +362,7 @@ describe('Phase Products', () => { it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when type updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phases/create.js b/src/routes/phases/create.js index b4da22a1..3b1904e9 100644 --- a/src/routes/phases/create.js +++ b/src/routes/phases/create.js @@ -58,7 +58,7 @@ module.exports = [ } if (data.startDate !== null && data.endDate !== null && data.startDate > data.endDate) { const err = new Error('startDate must not be after endDate.'); - err.status = 422; + err.status = 400; throw err; } return models.ProjectPhase @@ -95,11 +95,11 @@ module.exports = [ } // Get the product template - return models.ProductTemplate.findById(data.productTemplateId) + return models.ProductTemplate.findByPk(data.productTemplateId) .then((productTemplate) => { if (!productTemplate) { const err = new Error(`Product template does not exist with id = ${data.productTemplateId}`); - err.status = 422; + err.status = 400; throw err; } diff --git a/src/routes/phases/create.spec.js b/src/routes/phases/create.spec.js index 69f45a4d..88f1ab3c 100644 --- a/src/routes/phases/create.spec.js +++ b/src/routes/phases/create.spec.js @@ -135,7 +135,7 @@ describe('Project Phases', () => { describe('POST /projects/{id}/phases/', () => { it('should return 403 if user does not have permissions (non team member)', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -146,7 +146,7 @@ describe('Project Phases', () => { it('should return 403 if user does not have permissions (customer)', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -155,74 +155,74 @@ describe('Project Phases', () => { .expect(403, done); }); - it('should return 422 when name not provided', (done) => { + it('should return 400 when name not provided', (done) => { const reqBody = _.cloneDeep(body); delete reqBody.name; request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ param: reqBody }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 when status not provided', (done) => { + it('should return 400 when status not provided', (done) => { const reqBody = _.cloneDeep(body); delete reqBody.status; request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ param: reqBody }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 when startDate > endDate', (done) => { + it('should return 400 when startDate > endDate', (done) => { const reqBody = _.cloneDeep(body); reqBody.startDate = '2018-05-16T12:00:00'; request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ param: reqBody }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 when budget is negative', (done) => { + it('should return 400 when budget is negative', (done) => { const reqBody = _.cloneDeep(body); reqBody.budget = -20; request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ param: reqBody }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 when progress is negative', (done) => { + it('should return 400 when progress is negative', (done) => { const reqBody = _.cloneDeep(body); reqBody.progress = -20; request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ param: reqBody }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 404 when project is not found', (done) => { request(server) - .post('/v4/projects/99999/phases/') + .post('/v5/projects/99999/phases/') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -233,7 +233,7 @@ describe('Project Phases', () => { it('should return 201 if payload is valid', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -258,7 +258,7 @@ describe('Project Phases', () => { bodyWithZeros.budget = 0.0; bodyWithZeros.progress = 0.0; request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -278,7 +278,7 @@ describe('Project Phases', () => { it('should return 201 if payload has order specified', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -297,7 +297,7 @@ describe('Project Phases', () => { // Create second phase request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -321,7 +321,7 @@ describe('Project Phases', () => { it('should return 201 if payload has productTemplateId specified', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -366,7 +366,7 @@ describe('Project Phases', () => { it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when phase added', (done) => { request(server) - .post(`/v4/projects/${projectId}/phases/`) + .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phases/delete.spec.js b/src/routes/phases/delete.spec.js index 78453f39..2dfd9a66 100644 --- a/src/routes/phases/delete.spec.js +++ b/src/routes/phases/delete.spec.js @@ -30,7 +30,7 @@ const expectAfterDelete = (projectId, id, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/projects/${projectId}/phases/${id}`) + .get(`/v5/projects/${projectId}/phases/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -123,7 +123,7 @@ describe('Project Phases', () => { describe('DELETE /projects/{projectId}/phases/{phaseId}', () => { it('should return 403 if user does not have permissions (non team member)', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/${phaseId}`) + .delete(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -133,7 +133,7 @@ describe('Project Phases', () => { it('should return 403 if user does not have permissions (customer)', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/${phaseId}`) + .delete(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -143,7 +143,7 @@ describe('Project Phases', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .delete(`/v4/projects/999/phases/${phaseId}`) + .delete(`/v5/projects/999/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -153,7 +153,7 @@ describe('Project Phases', () => { it('should return 404 when no phase with specific phaseId', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/999`) + .delete(`/v5/projects/${projectId}/phases/999`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -163,7 +163,7 @@ describe('Project Phases', () => { it('should return 204 when user have project permission', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/${phaseId}`) + .delete(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -189,7 +189,7 @@ describe('Project Phases', () => { it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when phase removed', (done) => { request(server) - .delete(`/v4/projects/${projectId}/phases/${phaseId}`) + .delete(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phases/get.spec.js b/src/routes/phases/get.spec.js index f6cde663..3e3410d7 100644 --- a/src/routes/phases/get.spec.js +++ b/src/routes/phases/get.spec.js @@ -91,7 +91,7 @@ describe('Project Phases', () => { describe('GET /projects/{projectId}/phases/{phaseId}', () => { it('should return 403 when user have no permission (non team member)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}`) + .get(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -101,7 +101,7 @@ describe('Project Phases', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .get(`/v4/projects/999/phases/${phaseId}`) + .get(`/v5/projects/999/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -111,7 +111,7 @@ describe('Project Phases', () => { it('should return 404 when no phase with specific phaseId', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/999`) + .get(`/v5/projects/${projectId}/phases/999`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -121,7 +121,7 @@ describe('Project Phases', () => { it('should return 1 phase when user have project permission (customer)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}`) + .get(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -145,7 +145,7 @@ describe('Project Phases', () => { it('should return 1 phase when user have project permission (copilot)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/${phaseId}`) + .get(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phases/list-db.spec.js b/src/routes/phases/list-db.spec.js index 568f2815..cf1207d8 100644 --- a/src/routes/phases/list-db.spec.js +++ b/src/routes/phases/list-db.spec.js @@ -94,7 +94,7 @@ describe('Project Phases', () => { describe('GET /projects/{id}/phases/db', () => { it('should return 403 when user have no permission (non team member)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/db`) + .get(`/v5/projects/${projectId}/phases/db`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -105,7 +105,7 @@ describe('Project Phases', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .get('/v4/projects/999/phases/db') + .get('/v5/projects/999/phases/db') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -116,7 +116,7 @@ describe('Project Phases', () => { it('should return 1 phase when user have project permission (customer)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/db`) + .get(`/v5/projects/${projectId}/phases/db`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -137,7 +137,7 @@ describe('Project Phases', () => { it('should return 1 phase when user have project permission (copilot)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/db`) + .get(`/v5/projects/${projectId}/phases/db`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phases/list.spec.js b/src/routes/phases/list.spec.js index 43a1d14f..bcdfb877 100644 --- a/src/routes/phases/list.spec.js +++ b/src/routes/phases/list.spec.js @@ -110,7 +110,7 @@ describe('Project Phases', () => { describe('GET /projects/{id}/phases/', () => { it('should return 403 when user have no permission (non team member)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/`) + .get(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -121,7 +121,7 @@ describe('Project Phases', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .get('/v4/projects/999/phases/') + .get('/v5/projects/999/phases/') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -132,7 +132,7 @@ describe('Project Phases', () => { it('should return 1 phase when user have project permission (customer)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/`) + .get(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -153,7 +153,7 @@ describe('Project Phases', () => { it('should return 1 phase when user have project permission (copilot)', (done) => { request(server) - .get(`/v4/projects/${projectId}/phases/`) + .get(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phases/update.spec.js b/src/routes/phases/update.spec.js index 85e8e44d..a8353732 100644 --- a/src/routes/phases/update.spec.js +++ b/src/routes/phases/update.spec.js @@ -128,7 +128,7 @@ describe('Project Phases', () => { describe('PATCH /projects/{projectId}/phases/{phaseId}', () => { it('should return 403 if user does not have permissions (non team member)', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -139,7 +139,7 @@ describe('Project Phases', () => { it('should return 403 if user does not have permissions (customer)', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -150,7 +150,7 @@ describe('Project Phases', () => { it('should return 404 when no project with specific projectId', (done) => { request(server) - .patch(`/v4/projects/999/phases/${phaseId}`) + .patch(`/v5/projects/999/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -161,7 +161,7 @@ describe('Project Phases', () => { it('should return 404 when no phase with specific phaseId', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/999`) + .patch(`/v5/projects/${projectId}/phases/999`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -170,9 +170,9 @@ describe('Project Phases', () => { .expect(404, done); }); - it('should return 422 when parameters are invalid', (done) => { + it('should return 400 when parameters are invalid', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -182,12 +182,12 @@ describe('Project Phases', () => { }, }) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 400 when startDate > endDate', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -202,7 +202,7 @@ describe('Project Phases', () => { it('should return updated phase when user have permission and parameters are valid', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -227,7 +227,7 @@ describe('Project Phases', () => { bodyWithZeros.budget = 0.0; bodyWithZeros.progress = 0.0; request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -247,7 +247,7 @@ describe('Project Phases', () => { it('should return updated phase if the order is specified', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -291,7 +291,7 @@ describe('Project Phases', () => { it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when spentBudget updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -318,7 +318,7 @@ describe('Project Phases', () => { it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when progress updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -345,7 +345,7 @@ describe('Project Phases', () => { it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -373,7 +373,7 @@ describe('Project Phases', () => { it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (completed)', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -399,7 +399,7 @@ describe('Project Phases', () => { it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when budget updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -424,7 +424,7 @@ describe('Project Phases', () => { it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when startDate updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -458,7 +458,7 @@ describe('Project Phases', () => { it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when duration updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -490,7 +490,7 @@ describe('Project Phases', () => { it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when order updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -515,7 +515,7 @@ describe('Project Phases', () => { it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when endDate updated', (done) => { request(server) - .patch(`/v4/projects/${projectId}/phases/${phaseId}`) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/planConfig/revision/create.js b/src/routes/planConfig/revision/create.js index 00bbd164..52c0cb0f 100644 --- a/src/routes/planConfig/revision/create.js +++ b/src/routes/planConfig/revision/create.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; import util from '../../../util'; import models from '../../../models'; @@ -15,18 +16,16 @@ const schema = { key: Joi.string().max(45).required(), version: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - config: Joi.object().required(), + body: Joi.object().keys({ + config: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -43,13 +42,13 @@ module.exports = [ if (planConfig) { const version = planConfig ? planConfig.version : 1; const revision = planConfig ? planConfig.revision + 1 : 1; - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { version, revision, createdBy: req.authUser.userId, updatedBy: req.authUser.userId, key: req.params.key, - config: req.body.param.config, + config: req.body.config, }); return models.PlanConfig.create(entity); } @@ -57,9 +56,12 @@ module.exports = [ apiErr.status = 404; return Promise.reject(apiErr); }).then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PLAN_CONFIG_REVISION, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/planConfig/revision/create.spec.js b/src/routes/planConfig/revision/create.spec.js index 66a8121c..f1a92f83 100644 --- a/src/routes/planConfig/revision/create.spec.js +++ b/src/routes/planConfig/revision/create.spec.js @@ -35,32 +35,32 @@ describe('CREATE PlanConfig Revision', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('Post /projects/metadata/planConfig/{key}/versions/{version}/revision', () => { const body = { - param: { - config: { - 'test create': 'test create', - }, + config: { + 'test create': 'test create', }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/planConfig/dev/versions/1/revisions') + .post('/v5/projects/metadata/planConfig/dev/versions/1/revisions') .send(body) .expect(403, done); }); it('should return 404 if missing key', (done) => { request(server) - .post('/v4/projects/metadata/planConfig/no-exist-key/versions/1/revisions') + .post('/v5/projects/metadata/planConfig/no-exist-key/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -71,7 +71,7 @@ describe('CREATE PlanConfig Revision', () => { it('should return 404 if missing version', (done) => { request(server) - .post('/v4/projects/metadata/planConfig/dev/versions/100/revisions') + .post('/v5/projects/metadata/planConfig/dev/versions/100/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -80,26 +80,24 @@ describe('CREATE PlanConfig Revision', () => { .expect(404, done); }); - it('should return 422 if missing config', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - config: undefined, - }), - }; + it('should return 400 if missing config', (done) => { + const invalidBody = _.assign({}, body, { + config: undefined, + }); request(server) - .post('/v4/projects/metadata/planConfig/no-exist-key/versions/1/revisions') + .post('/v5/projects/metadata/planConfig/no-exist-key/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/planConfig/dev/versions/1/revisions') + .post('/v5/projects/metadata/planConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -107,9 +105,9 @@ describe('CREATE PlanConfig Revision', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.config.should.be.eql(body.param.config); + resJson.config.should.be.eql(body.config); resJson.key.should.be.eql('dev'); resJson.revision.should.be.eql(3); resJson.version.should.be.eql(1); @@ -125,7 +123,7 @@ describe('CREATE PlanConfig Revision', () => { it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/planConfig/dev/versions/1/revisions') + .post('/v5/projects/metadata/planConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/planConfig/revision/delete.js b/src/routes/planConfig/revision/delete.js index 0c890678..8728e000 100644 --- a/src/routes/planConfig/revision/delete.js +++ b/src/routes/planConfig/revision/delete.js @@ -2,8 +2,11 @@ * API to delete a revsion */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; +import util from '../../../util'; import models from '../../../models'; const permissions = tcMiddleware.permissions; @@ -38,9 +41,13 @@ module.exports = [ return planConfig.update({ deletedBy: req.authUser.userId, }); - }).then((planConfig) => { - planConfig.destroy(); - }).then(() => { + }).then(planConfig => + planConfig.destroy(), + ).then((planConfig) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.PLAN_CONFIG_REVISION, + _.pick(planConfig.toJSON(), 'id')); res.status(204).end(); }) .catch(next)); diff --git a/src/routes/planConfig/revision/delete.spec.js b/src/routes/planConfig/revision/delete.spec.js index 80802235..1d33284f 100644 --- a/src/routes/planConfig/revision/delete.spec.js +++ b/src/routes/planConfig/revision/delete.spec.js @@ -26,7 +26,7 @@ const expectAfterDelete = (err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -60,24 +60,26 @@ describe('DELETE planConfig revision', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/planConfig/{key}/versions/{version}/revisions/{revision}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1') .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -86,7 +88,7 @@ describe('DELETE planConfig revision', () => { it('should return 403 for copilot', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -95,7 +97,7 @@ describe('DELETE planConfig revision', () => { it('should return 403 for manager', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -104,7 +106,7 @@ describe('DELETE planConfig revision', () => { it('should return 404 for non-existed key', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/no-existed-key/versions/1/revisions/1') + .delete('/v5/projects/metadata/planConfig/no-existed-key/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -113,7 +115,7 @@ describe('DELETE planConfig revision', () => { it('should return 404 for non-existed version', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/100/revisions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/100/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -123,7 +125,7 @@ describe('DELETE planConfig revision', () => { it('should return 404 for non-existed revision', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/100') + .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/100') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -132,7 +134,7 @@ describe('DELETE planConfig revision', () => { it('should return 204, for admin', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -142,7 +144,7 @@ describe('DELETE planConfig revision', () => { it('should return 204, for connect admin', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/planConfig/revision/get.js b/src/routes/planConfig/revision/get.js index 2f20a564..08ee9617 100644 --- a/src/routes/planConfig/revision/get.js +++ b/src/routes/planConfig/revision/get.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -20,25 +20,58 @@ const schema = { module.exports = [ validate(schema), permissions('planConfig.view'), - (req, res, next) => models.PlanConfig.findOne({ - where: { - key: req.params.key, - version: req.params.version, - revision: req.params.revision, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((planConfig) => { - // Not found - if (!planConfig) { - const apiErr = new Error('PlanConfig not found for key' + - `${req.params.key} version ${req.params.version} revision ${req.params.revision}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => { + util.fetchByIdFromES('plan configs', { + query: { + nested: { + path: 'planConfigs', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'planConfigs.key': req.params.key } }, + { term: { 'planConfigs.version': req.params.version } }, + { term: { 'planConfigs.revision': req.params.revision } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No plan config found in ES'); + models.PlanConfig.findOne({ + where: { + key: req.params.key, + version: req.params.version, + revision: req.params.revision, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((planConfig) => { + // Not found + if (!planConfig) { + const apiErr = new Error('PlanConfig not found for key' + + `${req.params.key} version ${req.params.version} revision ${req.params.revision}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, planConfig)); - return Promise.resolve(); + res.json(planConfig); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('plan config found in ES'); + res.json(data[0].inner_hits.planConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } }) - .catch(next), + .catch(next); + }, ]; diff --git a/src/routes/planConfig/revision/get.spec.js b/src/routes/planConfig/revision/get.spec.js index 874aefd8..a524b5da 100644 --- a/src/routes/planConfig/revision/get.spec.js +++ b/src/routes/planConfig/revision/get.spec.js @@ -34,24 +34,26 @@ describe('GET a particular revision of specific version PlanConfig', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/planConfig/dev/versions/{version}/revisions/{revision}', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const planConfig = planConfigs[1]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(planConfig.key); resJson.config.should.be.eql(planConfig.config); @@ -68,13 +70,13 @@ describe('GET a particular revision of specific version PlanConfig', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -84,7 +86,7 @@ describe('GET a particular revision of specific version PlanConfig', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -94,7 +96,7 @@ describe('GET a particular revision of specific version PlanConfig', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -103,7 +105,7 @@ describe('GET a particular revision of specific version PlanConfig', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/planConfig/revision/list.js b/src/routes/planConfig/revision/list.js index a1bbb6ec..dc18059e 100644 --- a/src/routes/planConfig/revision/list.js +++ b/src/routes/planConfig/revision/list.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -19,23 +19,34 @@ const schema = { module.exports = [ validate(schema), permissions('planConfig.view'), - (req, res, next) => models.PlanConfig.findAll({ - where: { - key: req.params.key, - version: req.params.version, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((planConfigs) => { - // Not found - if ((!planConfigs) || (planConfigs.length === 0)) { - const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => + + util.fetchFromES('planConfigs') + .then((data) => { + if (data.planConfigs.length === 0) { + req.log.debug('No planConfig found in ES'); + models.PlanConfig.findAll({ + where: { + key: req.params.key, + version: req.params.version, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((planConfigs) => { + // Not found + if ((!planConfigs) || (planConfigs.length === 0)) { + const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, planConfigs)); - return Promise.resolve(); - }) - .catch(next), + res.json(planConfigs); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('planConfigs found in ES'); + res.json(data.planConfigs); + } + }).catch(next), ]; diff --git a/src/routes/planConfig/revision/list.spec.js b/src/routes/planConfig/revision/list.spec.js index 0781018c..38201af8 100644 --- a/src/routes/planConfig/revision/list.spec.js +++ b/src/routes/planConfig/revision/list.spec.js @@ -35,24 +35,26 @@ describe('LIST planConfig revisions', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/planConfig/dev/versions/{version}/revisions', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const planConfig = planConfigs[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].key.should.be.eql(planConfig.key); @@ -70,13 +72,13 @@ describe('LIST planConfig revisions', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -86,7 +88,7 @@ describe('LIST planConfig revisions', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -96,7 +98,7 @@ describe('LIST planConfig revisions', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -105,7 +107,7 @@ describe('LIST planConfig revisions', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/planConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/planConfig/version/create.js b/src/routes/planConfig/version/create.js index 50d4c09c..dc0526b1 100644 --- a/src/routes/planConfig/version/create.js +++ b/src/routes/planConfig/version/create.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; import util from '../../../util'; import models from '../../../models'; @@ -14,18 +15,16 @@ const schema = { params: { key: Joi.string().max(45).required(), }, - body: { - param: Joi.object().keys({ - config: Joi.object().required(), + body: Joi.object().keys({ + config: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -45,19 +44,22 @@ module.exports = [ latestVersion = latestVersionPlanConfig.version + 1; } - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { version: latestVersion, revision: 1, createdBy: req.authUser.userId, updatedBy: req.authUser.userId, key: req.params.key, - config: req.body.param.config, + config: req.body.config, }); return models.PlanConfig.create(entity); }).then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PLAN_CONFIG_VERSION, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/planConfig/version/create.spec.js b/src/routes/planConfig/version/create.spec.js index 80de2b60..1b564d7b 100644 --- a/src/routes/planConfig/version/create.spec.js +++ b/src/routes/planConfig/version/create.spec.js @@ -35,49 +35,47 @@ describe('CREATE PlanConfig version', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('Post /projects/metadata/planConfig/{key}/versions/', () => { const body = { - param: { - config: { - 'test create': 'test create', - }, + config: { + 'test create': 'test create', }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/planConfig/dev/versions') + .post('/v5/projects/metadata/planConfig/dev/versions') .send(body) .expect(403, done); }); - it('should return 422 if missing config', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - config: undefined, - }), - }; + it('should return 400 if missing config', (done) => { + const invalidBody = _.assign({}, body, { + config: undefined, + }); request(server) - .post('/v4/projects/metadata/planConfig/dev/versions/') + .post('/v5/projects/metadata/planConfig/dev/versions/') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/planConfig/dev/versions/') + .post('/v5/projects/metadata/planConfig/dev/versions/') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -85,9 +83,9 @@ describe('CREATE PlanConfig version', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.config.should.be.eql(body.param.config); + resJson.config.should.be.eql(body.config); resJson.key.should.be.eql('dev'); resJson.revision.should.be.eql(1); resJson.version.should.be.eql(2); @@ -103,7 +101,7 @@ describe('CREATE PlanConfig version', () => { it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/planConfig/dev/versions/') + .post('/v5/projects/metadata/planConfig/dev/versions/') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/planConfig/version/delete.js b/src/routes/planConfig/version/delete.js index a6b141c9..493fbdec 100644 --- a/src/routes/planConfig/version/delete.js +++ b/src/routes/planConfig/version/delete.js @@ -2,8 +2,11 @@ * API to add a project type */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; +import util from '../../../util'; import models from '../../../models'; const permissions = tcMiddleware.permissions; @@ -46,7 +49,21 @@ module.exports = [ key: req.params.key, version: req.params.version, }, - })).then(() => { + })) + .then(deleted => models.PlanConfig.findAll({ + where: { + key: req.params.key, + version: req.params.version, + }, + paranoid: false, + order: [['deletedAt', 'DESC']], + limit: deleted, + })) + .then((planConfigs) => { + _.map(planConfigs, planConfig => util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.PLAN_CONFIG_VERSION, + _.pick(planConfig.toJSON(), 'id'))); res.status(204).end(); }) .catch(next)); diff --git a/src/routes/planConfig/version/delete.spec.js b/src/routes/planConfig/version/delete.spec.js index c147fd8f..a47b5091 100644 --- a/src/routes/planConfig/version/delete.spec.js +++ b/src/routes/planConfig/version/delete.spec.js @@ -56,24 +56,26 @@ describe('DELETE planConfig version', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/planConfig/{key}/versions/{version}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1') .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -82,7 +84,7 @@ describe('DELETE planConfig version', () => { it('should return 403 for copilot', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -91,7 +93,7 @@ describe('DELETE planConfig version', () => { it('should return 403 for manager', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -100,7 +102,7 @@ describe('DELETE planConfig version', () => { it('should return 404 for non-existed key', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev111/versions/1') + .delete('/v5/projects/metadata/planConfig/dev111/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -109,7 +111,7 @@ describe('DELETE planConfig version', () => { it('should return 404 for non-existed version', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/111') + .delete('/v5/projects/metadata/planConfig/dev/versions/111') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -118,7 +120,7 @@ describe('DELETE planConfig version', () => { it('should return 204, for admin', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -128,7 +130,7 @@ describe('DELETE planConfig version', () => { it('should return 204, for connect admin', (done) => { request(server) - .delete('/v4/projects/metadata/planConfig/dev/versions/1') + .delete('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/planConfig/version/get.js b/src/routes/planConfig/version/get.js index 4b336122..dcb77840 100644 --- a/src/routes/planConfig/version/get.js +++ b/src/routes/planConfig/version/get.js @@ -5,8 +5,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -19,15 +19,49 @@ const schema = { module.exports = [ validate(schema), permissions('planConfig.view'), - (req, res, next) => models.PlanConfig.latestRevisionOfLatestVersion(req.params.key) - .then((form) => { - if (form == null) { - const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`); - apiErr.status = 404; - return Promise.reject(apiErr); + (req, res, next) => { + util.fetchByIdFromES('planConfigs', { + query: { + nested: { + path: 'planConfigs', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'planConfigs.key': req.params.key } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + sort: { 'planConfigs.version': 'desc' }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No planConfig found in ES'); + models.PlanConfig.latestRevisionOfLatestVersion(req.params.key) + .then((planConfig) => { + if (planConfig == null) { + const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + res.json(planConfig); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('planConfigs found in ES'); + res.json(data[0].inner_hits.planConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle } - res.json(util.wrapResponse(req.id, form)); - return Promise.resolve(); }) - .catch(next), + .catch(next); + }, + + ]; diff --git a/src/routes/planConfig/version/get.spec.js b/src/routes/planConfig/version/get.spec.js index f77d5cb3..d608a761 100644 --- a/src/routes/planConfig/version/get.spec.js +++ b/src/routes/planConfig/version/get.spec.js @@ -54,25 +54,27 @@ describe('GET a latest version of specific key of PlanConfig', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => models.PlanConfig.create(planConfigs[2])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1])) + .then(() => models.PlanConfig.create(planConfigs[2]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/planConfig/dev/versions/{version}', () => { it('should return 200 and correct version for admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev') + .get('/v5/projects/metadata/planConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const planConfig = planConfigs[2]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(planConfig.key); resJson.config.should.be.eql(planConfig.config); @@ -89,13 +91,13 @@ describe('GET a latest version of specific key of PlanConfig', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev') + .get('/v5/projects/metadata/planConfig/dev') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev') + .get('/v5/projects/metadata/planConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -105,7 +107,7 @@ describe('GET a latest version of specific key of PlanConfig', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev') + .get('/v5/projects/metadata/planConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -115,7 +117,7 @@ describe('GET a latest version of specific key of PlanConfig', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev') + .get('/v5/projects/metadata/planConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -124,7 +126,7 @@ describe('GET a latest version of specific key of PlanConfig', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev') + .get('/v5/projects/metadata/planConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/planConfig/version/getVersion.js b/src/routes/planConfig/version/getVersion.js index 556d8248..a5f10bef 100644 --- a/src/routes/planConfig/version/getVersion.js +++ b/src/routes/planConfig/version/getVersion.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -19,24 +19,55 @@ const schema = { module.exports = [ validate(schema), permissions('planConfig.view'), - (req, res, next) => models.PlanConfig.findOne({ - where: { - key: req.params.key, - version: req.params.version, + (req, res, next) => + util.fetchByIdFromES('planConfigs', { + query: { + nested: { + path: 'planConfigs', + query: { + filtered: { + filter: { + bool: { + must: [ + { term: { 'planConfigs.key': req.params.key } }, + { term: { 'planConfigs.version': req.params.version } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, }, - order: [['revision', 'DESC']], - limit: 1, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, + sort: { 'planConfigs.revision': 'desc' }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No planConfig found in ES'); + models.PlanConfig.findOne({ + where: { + key: req.params.key, + version: req.params.version, + }, + order: [['revision', 'DESC']], + limit: 1, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((planConfig) => { + // Not found + if (!planConfig) { + const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + res.json(planConfig); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('planConfigs found in ES'); + res.json(data[0].inner_hits.planConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } }) - .then((planConfig) => { - // Not found - if (!planConfig) { - const apiErr = new Error(`PlanConfig not found for key ${req.params.key} version ${req.params.version}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } - res.json(util.wrapResponse(req.id, planConfig)); - return Promise.resolve(); - }) - .catch(next), + .catch(next), ]; diff --git a/src/routes/planConfig/version/getVersion.spec.js b/src/routes/planConfig/version/getVersion.spec.js index ffca3809..3052a224 100644 --- a/src/routes/planConfig/version/getVersion.spec.js +++ b/src/routes/planConfig/version/getVersion.spec.js @@ -44,25 +44,27 @@ describe('GET a particular version of specific key of PlanConfig', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => models.PlanConfig.create(planConfigs[2])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1])) + .then(() => models.PlanConfig.create(planConfigs[2]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/planConfig/dev/versions/{version}', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1') + .get('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const planConfig = planConfigs[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(planConfig.key); resJson.config.should.be.eql(planConfig.config); @@ -79,13 +81,13 @@ describe('GET a particular version of specific key of PlanConfig', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1') + .get('/v5/projects/metadata/planConfig/dev/versions/1') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev') + .get('/v5/projects/metadata/planConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -95,7 +97,7 @@ describe('GET a particular version of specific key of PlanConfig', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1') + .get('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -105,7 +107,7 @@ describe('GET a particular version of specific key of PlanConfig', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1') + .get('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -114,7 +116,7 @@ describe('GET a particular version of specific key of PlanConfig', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions/1') + .get('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/planConfig/version/list.js b/src/routes/planConfig/version/list.js index 5624a981..66c857d9 100644 --- a/src/routes/planConfig/version/list.js +++ b/src/routes/planConfig/version/list.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -18,30 +18,42 @@ const schema = { module.exports = [ validate(schema), permissions('planConfig.view'), - (req, res, next) => models.PlanConfig.findAll({ - where: { - key: req.params.key, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((planConfigs) => { - // Not found - if ((!planConfigs) || (planConfigs.length === 0)) { - const apiErr = new Error(`PlanConfig not found for key ${req.params.key}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => + util.fetchFromES('planConfigs') + .then((data) => { + if (data.planConfigs.length === 0) { + req.log.debug('No planConfig found in ES'); + models.PlanConfig.findAll({ + where: { + key: req.params.key, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((planConfigs) => { + // Not found + if ((!planConfigs) || (planConfigs.length === 0)) { + const apiErr = new Error(`PlanConfig not found for key ${req.params.key}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + const latestPlanConfigs = {}; + planConfigs.forEach((element) => { + const isNewerRevision = (latestPlanConfigs[element.version] != null) && + (latestPlanConfigs[element.version].revision < element.revision); + if ((latestPlanConfigs[element.version] == null) || isNewerRevision) { + latestPlanConfigs[element.version] = element; + } + }); + res.json(Object.values(latestPlanConfigs)); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('planConfigs found in ES'); + res.json(data.planConfigs); + } + }).catch(next), + - const latestPlanConfigs = {}; - planConfigs.forEach((element) => { - const isNewerRevision = (latestPlanConfigs[element.version] != null) && - (latestPlanConfigs[element.version].revision < element.revision); - if ((latestPlanConfigs[element.version] == null) || isNewerRevision) { - latestPlanConfigs[element.version] = element; - } - }); - res.json(util.wrapResponse(req.id, Object.values(latestPlanConfigs))); - return Promise.resolve(); - }) - .catch(next), ]; diff --git a/src/routes/planConfig/version/list.spec.js b/src/routes/planConfig/version/list.spec.js index 851a9577..8fc3ce1b 100644 --- a/src/routes/planConfig/version/list.spec.js +++ b/src/routes/planConfig/version/list.spec.js @@ -35,24 +35,26 @@ describe('LIST planConfig versions', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/planConfig/dev/versions/{version}', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions') + .get('/v5/projects/metadata/planConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const planConfig = planConfigs[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].key.should.be.eql(planConfig.key); @@ -70,13 +72,13 @@ describe('LIST planConfig versions', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions') + .get('/v5/projects/metadata/planConfig/dev/versions') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions') + .get('/v5/projects/metadata/planConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -86,7 +88,7 @@ describe('LIST planConfig versions', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions') + .get('/v5/projects/metadata/planConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -96,7 +98,7 @@ describe('LIST planConfig versions', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions') + .get('/v5/projects/metadata/planConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -105,7 +107,7 @@ describe('LIST planConfig versions', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/planConfig/dev/versions') + .get('/v5/projects/metadata/planConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/planConfig/version/update.js b/src/routes/planConfig/version/update.js index 44591e6c..b62b63c9 100644 --- a/src/routes/planConfig/version/update.js +++ b/src/routes/planConfig/version/update.js @@ -7,6 +7,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; import util from '../../../util'; import models from '../../../models'; @@ -17,18 +18,16 @@ const schema = { version: Joi.number().integer().positive().required(), key: Joi.string().max(45).required(), }, - body: { - param: Joi.object().keys({ - config: Joi.object().required(), + body: Joi.object().keys({ + config: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -60,14 +59,17 @@ module.exports = [ createdBy: req.authUser.userId, updatedBy: req.authUser.userId, key: req.params.key, - config: req.body.param.config, + config: req.body.config, }; return models.PlanConfig.create(entity); }) .then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PLAN_CONFIG_VERSION, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/planConfig/version/update.spec.js b/src/routes/planConfig/version/update.spec.js index 537b8c57..b59283c1 100644 --- a/src/routes/planConfig/version/update.spec.js +++ b/src/routes/planConfig/version/update.spec.js @@ -35,48 +35,46 @@ describe('UPDATE PlanConfig version', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PlanConfig.create(planConfigs[0])) - .then(() => models.PlanConfig.create(planConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PlanConfig.create(planConfigs[0])) + .then(() => models.PlanConfig.create(planConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('Post /projects/metadata/planConfig/{key}/versions/{version}', () => { const body = { - param: { - config: { - 'test create': 'test create', - }, + config: { + 'test create': 'test create', }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .patch('/v4/projects/metadata/planConfig/dev/versions/1') + .patch('/v5/projects/metadata/planConfig/dev/versions/1') .send(body) .expect(403, done); }); - it('should return 422 if missing config', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - config: undefined, - }), - }; + it('should return 400 if missing config', (done) => { + const invalidBody = _.assign({}, body, { + config: undefined, + }); request(server) - .patch('/v4/projects/metadata/planConfig/dev/versions/1') + .patch('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .patch('/v4/projects/metadata/planConfig/dev/versions/1') + .patch('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -84,9 +82,9 @@ describe('UPDATE PlanConfig version', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.config.should.be.eql(body.param.config); + resJson.config.should.be.eql(body.config); resJson.key.should.be.eql('dev'); resJson.revision.should.be.eql(3); resJson.version.should.be.eql(1); @@ -102,7 +100,7 @@ describe('UPDATE PlanConfig version', () => { it('should return 403 for member', (done) => { request(server) - .patch('/v4/projects/metadata/planConfig/dev/versions/1') + .patch('/v5/projects/metadata/planConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/priceConfig/revision/create.js b/src/routes/priceConfig/revision/create.js index 83790b82..294f176f 100644 --- a/src/routes/priceConfig/revision/create.js +++ b/src/routes/priceConfig/revision/create.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; import util from '../../../util'; import models from '../../../models'; @@ -15,18 +16,16 @@ const schema = { key: Joi.string().max(45).required(), version: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - config: Joi.object().required(), + body: Joi.object().keys({ + config: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -43,13 +42,13 @@ module.exports = [ if (priceConfig) { const version = priceConfig ? priceConfig.version : 1; const revision = priceConfig ? priceConfig.revision + 1 : 1; - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { version, revision, createdBy: req.authUser.userId, updatedBy: req.authUser.userId, key: req.params.key, - config: req.body.param.config, + config: req.body.config, }); return models.PriceConfig.create(entity); } @@ -57,9 +56,12 @@ module.exports = [ apiErr.status = 404; return Promise.reject(apiErr); }).then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PRICE_CONFIG_REVISION, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/priceConfig/revision/create.spec.js b/src/routes/priceConfig/revision/create.spec.js index b082eddc..537f043f 100644 --- a/src/routes/priceConfig/revision/create.spec.js +++ b/src/routes/priceConfig/revision/create.spec.js @@ -35,32 +35,33 @@ describe('CREATE PriceConfig Revision', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('Post /projects/metadata/priceConfig/{key}/versions/{version}/revision', () => { const body = { - param: { - config: { - 'test create': 'test create', - }, + + config: { + 'test create': 'test create', }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/priceConfig/dev/versions/1/revisions') + .post('/v5/projects/metadata/priceConfig/dev/versions/1/revisions') .send(body) .expect(403, done); }); it('should return 404 if missing key', (done) => { request(server) - .post('/v4/projects/metadata/priceConfig/no-exist-key/versions/1/revisions') + .post('/v5/projects/metadata/priceConfig/no-exist-key/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -71,7 +72,7 @@ describe('CREATE PriceConfig Revision', () => { it('should return 404 if missing version', (done) => { request(server) - .post('/v4/projects/metadata/priceConfig/dev/versions/100/revisions') + .post('/v5/projects/metadata/priceConfig/dev/versions/100/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -80,26 +81,24 @@ describe('CREATE PriceConfig Revision', () => { .expect(404, done); }); - it('should return 422 if missing config', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - config: undefined, - }), - }; + it('should return 400 if missing config', (done) => { + const invalidBody = _.assign({}, body, { + config: undefined, + }); request(server) - .post('/v4/projects/metadata/priceConfig/no-exist-key/versions/1/revisions') + .post('/v5/projects/metadata/priceConfig/no-exist-key/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/priceConfig/dev/versions/1/revisions') + .post('/v5/projects/metadata/priceConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -107,9 +106,9 @@ describe('CREATE PriceConfig Revision', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.config.should.be.eql(body.param.config); + resJson.config.should.be.eql(body.config); resJson.key.should.be.eql('dev'); resJson.revision.should.be.eql(3); resJson.version.should.be.eql(1); @@ -125,7 +124,7 @@ describe('CREATE PriceConfig Revision', () => { it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/priceConfig/dev/versions/1/revisions') + .post('/v5/projects/metadata/priceConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/priceConfig/revision/delete.js b/src/routes/priceConfig/revision/delete.js index 3ce81743..a529ba58 100644 --- a/src/routes/priceConfig/revision/delete.js +++ b/src/routes/priceConfig/revision/delete.js @@ -2,8 +2,11 @@ * API to delete a revsion */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; +import util from '../../../util'; import models from '../../../models'; const permissions = tcMiddleware.permissions; @@ -38,9 +41,13 @@ module.exports = [ return priceConfig.update({ deletedBy: req.authUser.userId, }); - }).then((priceConfig) => { - priceConfig.destroy(); - }).then(() => { + }).then(priceConfig => + priceConfig.destroy(), + ).then((priceConfig) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.PRICE_CONFIG_REVISION, + _.pick(priceConfig.toJSON(), 'id')); res.status(204).end(); }) .catch(next)); diff --git a/src/routes/priceConfig/revision/delete.spec.js b/src/routes/priceConfig/revision/delete.spec.js index 0acc1491..4ab18cda 100644 --- a/src/routes/priceConfig/revision/delete.spec.js +++ b/src/routes/priceConfig/revision/delete.spec.js @@ -26,7 +26,7 @@ const expectAfterDelete = (err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -60,24 +60,25 @@ describe('DELETE priceConfig revision', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); - + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/priceConfig/{key}/versions/{version}/revisions/{revision}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1') .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -86,7 +87,7 @@ describe('DELETE priceConfig revision', () => { it('should return 403 for copilot', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -95,7 +96,7 @@ describe('DELETE priceConfig revision', () => { it('should return 403 for manager', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -104,7 +105,7 @@ describe('DELETE priceConfig revision', () => { it('should return 404 for non-existed key', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/no-existed-key/versions/1/revisions/1') + .delete('/v5/projects/metadata/priceConfig/no-existed-key/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -113,7 +114,7 @@ describe('DELETE priceConfig revision', () => { it('should return 404 for non-existed version', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/100/revisions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/100/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -123,7 +124,7 @@ describe('DELETE priceConfig revision', () => { it('should return 404 for non-existed revision', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/100') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/100') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -132,7 +133,7 @@ describe('DELETE priceConfig revision', () => { it('should return 204, for admin', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -142,7 +143,7 @@ describe('DELETE priceConfig revision', () => { it('should return 204, for connect admin', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/priceConfig/revision/get.js b/src/routes/priceConfig/revision/get.js index 081f051c..34ff6ad7 100644 --- a/src/routes/priceConfig/revision/get.js +++ b/src/routes/priceConfig/revision/get.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -20,25 +20,58 @@ const schema = { module.exports = [ validate(schema), permissions('priceConfig.view'), - (req, res, next) => models.PriceConfig.findOne({ - where: { - key: req.params.key, - version: req.params.version, - revision: req.params.revision, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((priceConfig) => { - // Not found - if (!priceConfig) { - const apiErr = new Error('PriceConfig not found for key' + - ` ${req.params.key} version ${req.params.version} revision ${req.params.revision}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => { + util.fetchByIdFromES('priceConfigs', { + query: { + nested: { + path: 'priceConfigs', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'priceConfigs.key': req.params.key } }, + { term: { 'priceConfigs.version': req.params.version } }, + { term: { 'priceConfigs.revision': req.params.revision } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No priceConfi found in ES'); + models.PriceConfig.findOne({ + where: { + key: req.params.key, + version: req.params.version, + revision: req.params.revision, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((priceConfig) => { + // Not found + if (!priceConfig) { + const apiErr = new Error('PriceConfig not found for key' + + ` ${req.params.key} version ${req.params.version} revision ${req.params.revision}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, priceConfig)); - return Promise.resolve(); + res.json(priceConfig); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('priceConfigs found in ES'); + res.json(data[0].inner_hits.priceConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } }) - .catch(next), + .catch(next); + }, ]; diff --git a/src/routes/priceConfig/revision/get.spec.js b/src/routes/priceConfig/revision/get.spec.js index 7830f440..65f6e72b 100644 --- a/src/routes/priceConfig/revision/get.spec.js +++ b/src/routes/priceConfig/revision/get.spec.js @@ -34,24 +34,26 @@ describe('GET a particular revision of specific version PriceConfig', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/priceConfig/dev/versions/{version}/revisions/{revision}', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const priceConfig = priceConfigs[1]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(priceConfig.key); resJson.config.should.be.eql(priceConfig.config); @@ -68,13 +70,13 @@ describe('GET a particular revision of specific version PriceConfig', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -84,7 +86,7 @@ describe('GET a particular revision of specific version PriceConfig', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -94,7 +96,7 @@ describe('GET a particular revision of specific version PriceConfig', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -103,7 +105,7 @@ describe('GET a particular revision of specific version PriceConfig', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions/2') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions/2') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/priceConfig/revision/list.js b/src/routes/priceConfig/revision/list.js index 1d67a9d1..bbfa404d 100644 --- a/src/routes/priceConfig/revision/list.js +++ b/src/routes/priceConfig/revision/list.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -19,23 +19,34 @@ const schema = { module.exports = [ validate(schema), permissions('priceConfig.view'), - (req, res, next) => models.PriceConfig.findAll({ - where: { - key: req.params.key, - version: req.params.version, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((priceConfigs) => { - // Not found - if ((!priceConfigs) || (priceConfigs.length === 0)) { - const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => + util.fetchFromES('priceConfigs') + .then((data) => { + if (data.priceConfigs.length === 0) { + req.log.debug('No priceConfig found in ES'); + models.PriceConfig.findAll({ + where: { + key: req.params.key, + version: req.params.version, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((priceConfigs) => { + // Not found + if ((!priceConfigs) || (priceConfigs.length === 0)) { + const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + res.json(priceConfigs); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('priceConfigs found in ES'); + res.json(data.priceConfigs); + } + }).catch(next), - res.json(util.wrapResponse(req.id, priceConfigs)); - return Promise.resolve(); - }) - .catch(next), ]; diff --git a/src/routes/priceConfig/revision/list.spec.js b/src/routes/priceConfig/revision/list.spec.js index 04363891..04623fda 100644 --- a/src/routes/priceConfig/revision/list.spec.js +++ b/src/routes/priceConfig/revision/list.spec.js @@ -35,24 +35,26 @@ describe('LIST priceConfig revisions', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/priceConfig/dev/versions/{version}/revisions', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const priceConfig = priceConfigs[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].key.should.be.eql(priceConfig.key); @@ -70,13 +72,13 @@ describe('LIST priceConfig revisions', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -86,7 +88,7 @@ describe('LIST priceConfig revisions', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -96,7 +98,7 @@ describe('LIST priceConfig revisions', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -105,7 +107,7 @@ describe('LIST priceConfig revisions', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1/revisions') + .get('/v5/projects/metadata/priceConfig/dev/versions/1/revisions') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/priceConfig/version/create.js b/src/routes/priceConfig/version/create.js index d3c71335..504e9f7c 100644 --- a/src/routes/priceConfig/version/create.js +++ b/src/routes/priceConfig/version/create.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; import util from '../../../util'; import models from '../../../models'; @@ -14,18 +15,18 @@ const schema = { params: { key: Joi.string().max(45).required(), }, - body: { - param: Joi.object().keys({ - config: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + config: Joi.object().required(), + + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), + }; module.exports = [ @@ -45,19 +46,22 @@ module.exports = [ latestVersion = latestVersionPriceConfig.version + 1; } - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { version: latestVersion, revision: 1, createdBy: req.authUser.userId, updatedBy: req.authUser.userId, key: req.params.key, - config: req.body.param.config, + config: req.body.config, }); return models.PriceConfig.create(entity); }).then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PRICE_CONFIG_VERSION, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/priceConfig/version/create.spec.js b/src/routes/priceConfig/version/create.spec.js index 6ad58665..23a659e4 100644 --- a/src/routes/priceConfig/version/create.spec.js +++ b/src/routes/priceConfig/version/create.spec.js @@ -35,49 +35,47 @@ describe('CREATE PriceConfig version', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('Post /projects/metadata/priceConfig/{key}/versions/', () => { const body = { - param: { - config: { - 'test create': 'test create', - }, + config: { + 'test create': 'test create', }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/priceConfig/dev/versions') + .post('/v5/projects/metadata/priceConfig/dev/versions') .send(body) .expect(403, done); }); - it('should return 422 if missing config', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - config: undefined, - }), - }; + it('should return 400 if missing config', (done) => { + const invalidBody = _.assign({}, body, { + config: undefined, + }); request(server) - .post('/v4/projects/metadata/priceConfig/dev/versions/') + .post('/v5/projects/metadata/priceConfig/dev/versions/') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/priceConfig/dev/versions/') + .post('/v5/projects/metadata/priceConfig/dev/versions/') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -85,9 +83,9 @@ describe('CREATE PriceConfig version', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.config.should.be.eql(body.param.config); + resJson.config.should.be.eql(body.config); resJson.key.should.be.eql('dev'); resJson.revision.should.be.eql(1); resJson.version.should.be.eql(2); @@ -103,7 +101,7 @@ describe('CREATE PriceConfig version', () => { it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/priceConfig/dev/versions/') + .post('/v5/projects/metadata/priceConfig/dev/versions/') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/priceConfig/version/delete.js b/src/routes/priceConfig/version/delete.js index 3aa4bc66..3a9eaf28 100644 --- a/src/routes/priceConfig/version/delete.js +++ b/src/routes/priceConfig/version/delete.js @@ -2,8 +2,11 @@ * API to add a project type */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; +import util from '../../../util'; import models from '../../../models'; const permissions = tcMiddleware.permissions; @@ -46,7 +49,21 @@ module.exports = [ key: req.params.key, version: req.params.version, }, - })).then(() => { + })) + .then(deleted => models.PriceConfig.findAll({ + where: { + key: req.params.key, + version: req.params.version, + }, + paranoid: false, + order: [['deletedAt', 'DESC']], + limit: deleted, + })) + .then((priceConfigs) => { + _.map(priceConfigs, priceConfig => util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.PRICE_CONFIG_VERSION, + _.pick(priceConfig.toJSON(), 'id'))); res.status(204).end(); }) .catch(next)); diff --git a/src/routes/priceConfig/version/delete.spec.js b/src/routes/priceConfig/version/delete.spec.js index 2bcac11f..534ffc82 100644 --- a/src/routes/priceConfig/version/delete.spec.js +++ b/src/routes/priceConfig/version/delete.spec.js @@ -56,24 +56,26 @@ describe('DELETE priceConfig version', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/priceConfig/{key}/versions/{version}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1') .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -82,7 +84,7 @@ describe('DELETE priceConfig version', () => { it('should return 403 for copilot', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -91,7 +93,7 @@ describe('DELETE priceConfig version', () => { it('should return 403 for manager', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -100,7 +102,7 @@ describe('DELETE priceConfig version', () => { it('should return 404 for non-existed key', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev111/versions/1') + .delete('/v5/projects/metadata/priceConfig/dev111/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -109,7 +111,7 @@ describe('DELETE priceConfig version', () => { it('should return 404 for non-existed version', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/111') + .delete('/v5/projects/metadata/priceConfig/dev/versions/111') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -118,7 +120,7 @@ describe('DELETE priceConfig version', () => { it('should return 204, for admin', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -128,7 +130,7 @@ describe('DELETE priceConfig version', () => { it('should return 204, for connect admin', (done) => { request(server) - .delete('/v4/projects/metadata/priceConfig/dev/versions/1') + .delete('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/priceConfig/version/get.js b/src/routes/priceConfig/version/get.js index da624c3b..c01348a0 100644 --- a/src/routes/priceConfig/version/get.js +++ b/src/routes/priceConfig/version/get.js @@ -5,8 +5,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -19,15 +19,48 @@ const schema = { module.exports = [ validate(schema), permissions('priceConfig.view'), - (req, res, next) => models.PriceConfig.latestRevisionOfLatestVersion(req.params.key) - .then((form) => { - if (form == null) { - const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } - res.json(util.wrapResponse(req.id, form)); - return Promise.resolve(); - }) -.catch(next), + (req, res, next) => { + util.fetchByIdFromES('priceConfigs', { + query: { + nested: { + path: 'priceConfigs', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'priceConfigs.key': req.params.key } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + sort: { 'priceConfigs.version': 'desc' }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No priceConfig found in ES'); + + models.PriceConfig.latestRevisionOfLatestVersion(req.params.key) + .then((priceConfig) => { + if (priceConfig == null) { + const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + res.json(priceConfig); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('priceConfigs found in ES'); + res.json(data[0].inner_hits.priceConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } + }) + .catch(next); + }, ]; diff --git a/src/routes/priceConfig/version/get.spec.js b/src/routes/priceConfig/version/get.spec.js index d76afd60..e7062502 100644 --- a/src/routes/priceConfig/version/get.spec.js +++ b/src/routes/priceConfig/version/get.spec.js @@ -54,25 +54,27 @@ describe('GET a latest version of specific key of PriceConfig', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => models.PriceConfig.create(priceConfigs[2])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1])) + .then(() => models.PriceConfig.create(priceConfigs[2]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/priceConfig/dev/versions/{version}', () => { it('should return 200 and correct version for admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev') + .get('/v5/projects/metadata/priceConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const priceConfig = priceConfigs[2]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(priceConfig.key); resJson.config.should.be.eql(priceConfig.config); @@ -89,13 +91,13 @@ describe('GET a latest version of specific key of PriceConfig', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev') + .get('/v5/projects/metadata/priceConfig/dev') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev') + .get('/v5/projects/metadata/priceConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -105,7 +107,7 @@ describe('GET a latest version of specific key of PriceConfig', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev') + .get('/v5/projects/metadata/priceConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -115,7 +117,7 @@ describe('GET a latest version of specific key of PriceConfig', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev') + .get('/v5/projects/metadata/priceConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -124,7 +126,7 @@ describe('GET a latest version of specific key of PriceConfig', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev') + .get('/v5/projects/metadata/priceConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/priceConfig/version/getVersion.js b/src/routes/priceConfig/version/getVersion.js index 8dd9d17c..54c1bad4 100644 --- a/src/routes/priceConfig/version/getVersion.js +++ b/src/routes/priceConfig/version/getVersion.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -19,24 +19,56 @@ const schema = { module.exports = [ validate(schema), permissions('priceConfig.view'), - (req, res, next) => models.PriceConfig.findOne({ - where: { - key: req.params.key, - version: req.params.version, - }, - order: [['revision', 'DESC']], - limit: 1, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((priceConfig) => { - // Not found - if (!priceConfig) { - const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`); - apiErr.status = 404; - return Promise.reject(apiErr); + (req, res, next) => { + util.fetchByIdFromES('priceConfigs', { + query: { + nested: { + path: 'priceConfigs', + query: { + filtered: { + filter: { + bool: { + must: [ + { term: { 'priceConfigs.key': req.params.key } }, + { term: { 'priceConfigs.version': req.params.version } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + sort: { 'priceConfigs.revision': 'desc' }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No priceConfig found in ES'); + models.PriceConfig.findOne({ + where: { + key: req.params.key, + version: req.params.version, + }, + order: [['revision', 'DESC']], + limit: 1, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((priceConfig) => { + // Not found + if (!priceConfig) { + const apiErr = new Error(`PriceConfig not found for key ${req.params.key} version ${req.params.version}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + res.json(priceConfig); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('priceConfigs found in ES'); + res.json(data[0].inner_hits.priceConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle } - res.json(util.wrapResponse(req.id, priceConfig)); - return Promise.resolve(); }) - .catch(next), + .catch(next); + }, ]; diff --git a/src/routes/priceConfig/version/getVersion.spec.js b/src/routes/priceConfig/version/getVersion.spec.js index 04671910..7f693610 100644 --- a/src/routes/priceConfig/version/getVersion.spec.js +++ b/src/routes/priceConfig/version/getVersion.spec.js @@ -44,25 +44,27 @@ describe('GET a particular version of specific key of PriceConfig', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => models.PriceConfig.create(priceConfigs[2])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1])) + .then(() => models.PriceConfig.create(priceConfigs[2]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/priceConfig/dev/versions/{version}', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1') + .get('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const priceConfig = priceConfigs[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(priceConfig.key); resJson.config.should.be.eql(priceConfig.config); @@ -79,13 +81,13 @@ describe('GET a particular version of specific key of PriceConfig', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1') + .get('/v5/projects/metadata/priceConfig/dev/versions/1') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev') + .get('/v5/projects/metadata/priceConfig/dev') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -95,7 +97,7 @@ describe('GET a particular version of specific key of PriceConfig', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1') + .get('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -105,7 +107,7 @@ describe('GET a particular version of specific key of PriceConfig', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1') + .get('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -114,7 +116,7 @@ describe('GET a particular version of specific key of PriceConfig', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions/1') + .get('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/priceConfig/version/list.js b/src/routes/priceConfig/version/list.js index aa4e9e77..a8257e94 100644 --- a/src/routes/priceConfig/version/list.js +++ b/src/routes/priceConfig/version/list.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../../util'; import models from '../../../models'; +import util from '../../../util'; const permissions = tcMiddleware.permissions; @@ -18,30 +18,40 @@ const schema = { module.exports = [ validate(schema), permissions('priceConfig.view'), - (req, res, next) => models.PriceConfig.findAll({ - where: { - key: req.params.key, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((priceConfigs) => { - // Not found - if ((!priceConfigs) || (priceConfigs.length === 0)) { - const apiErr = new Error(`PriceConfig not found for key ${req.params.key}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => + util.fetchFromES('priceConfigs') + .then((data) => { + if (data.priceConfigs.length === 0) { + req.log.debug('No priceConfig found in ES'); + models.PriceConfig.findAll({ + where: { + key: req.params.key, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((priceConfigs) => { + // Not found + if ((!priceConfigs) || (priceConfigs.length === 0)) { + const apiErr = new Error(`PriceConfig not found for key ${req.params.key}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - const latestPriceConfigs = {}; - priceConfigs.forEach((element) => { - const isNewerRevision = (latestPriceConfigs[element.version] != null) && - (latestPriceConfigs[element.version].revision < element.revision); - if ((latestPriceConfigs[element.version] == null) || isNewerRevision) { - latestPriceConfigs[element.version] = element; - } - }); - res.json(util.wrapResponse(req.id, Object.values(latestPriceConfigs))); - return Promise.resolve(); - }) - .catch(next), + const latestPriceConfigs = {}; + priceConfigs.forEach((element) => { + const isNewerRevision = (latestPriceConfigs[element.version] != null) && + (latestPriceConfigs[element.version].revision < element.revision); + if ((latestPriceConfigs[element.version] == null) || isNewerRevision) { + latestPriceConfigs[element.version] = element; + } + }); + res.json(Object.values(latestPriceConfigs)); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('priceConfigs found in ES'); + res.json(data.priceConfigs); + } + }).catch(next), ]; diff --git a/src/routes/priceConfig/version/list.spec.js b/src/routes/priceConfig/version/list.spec.js index 2c58aca0..ec245406 100644 --- a/src/routes/priceConfig/version/list.spec.js +++ b/src/routes/priceConfig/version/list.spec.js @@ -35,24 +35,26 @@ describe('LIST priceConfig versions', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/priceConfig/dev/versions/{version}', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions') + .get('/v5/projects/metadata/priceConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { const priceConfig = priceConfigs[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].key.should.be.eql(priceConfig.key); @@ -70,13 +72,13 @@ describe('LIST priceConfig versions', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions') + .get('/v5/projects/metadata/priceConfig/dev/versions') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions') + .get('/v5/projects/metadata/priceConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -86,7 +88,7 @@ describe('LIST priceConfig versions', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions') + .get('/v5/projects/metadata/priceConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -96,7 +98,7 @@ describe('LIST priceConfig versions', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions') + .get('/v5/projects/metadata/priceConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -105,7 +107,7 @@ describe('LIST priceConfig versions', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/priceConfig/dev/versions') + .get('/v5/projects/metadata/priceConfig/dev/versions') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/priceConfig/version/update.js b/src/routes/priceConfig/version/update.js index 6b7a7e44..1a58138d 100644 --- a/src/routes/priceConfig/version/update.js +++ b/src/routes/priceConfig/version/update.js @@ -7,6 +7,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../../constants'; import util from '../../../util'; import models from '../../../models'; @@ -17,18 +18,18 @@ const schema = { version: Joi.number().integer().positive().required(), key: Joi.string().max(45).required(), }, - body: { - param: Joi.object().keys({ - config: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + config: Joi.object().required(), + + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), + }; module.exports = [ @@ -60,14 +61,17 @@ module.exports = [ createdBy: req.authUser.userId, updatedBy: req.authUser.userId, key: req.params.key, - config: req.body.param.config, + config: req.body.config, }; return models.PriceConfig.create(entity); }) .then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PRICE_CONFIG_VERSION, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/priceConfig/version/update.spec.js b/src/routes/priceConfig/version/update.spec.js index 3a9cad6e..ebb4d853 100644 --- a/src/routes/priceConfig/version/update.spec.js +++ b/src/routes/priceConfig/version/update.spec.js @@ -35,48 +35,46 @@ describe('UPDATE PriceConfig version', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.PriceConfig.create(priceConfigs[0])) - .then(() => models.PriceConfig.create(priceConfigs[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.PriceConfig.create(priceConfigs[0])) + .then(() => models.PriceConfig.create(priceConfigs[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('Post /projects/metadata/priceConfig/{key}/versions/{version}', () => { const body = { - param: { - config: { - 'test create': 'test create', - }, + config: { + 'test create': 'test create', }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .patch('/v4/projects/metadata/priceConfig/dev/versions/1') + .patch('/v5/projects/metadata/priceConfig/dev/versions/1') .send(body) .expect(403, done); }); - it('should return 422 if missing config', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - config: undefined, - }), - }; + it('should return 400 if missing config', (done) => { + const invalidBody = _.assign({}, body, { + config: undefined, + }); request(server) - .patch('/v4/projects/metadata/priceConfig/dev/versions/1') + .patch('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .patch('/v4/projects/metadata/priceConfig/dev/versions/1') + .patch('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -84,9 +82,9 @@ describe('UPDATE PriceConfig version', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.config.should.be.eql(body.param.config); + resJson.config.should.be.eql(body.config); resJson.key.should.be.eql('dev'); resJson.revision.should.be.eql(3); resJson.version.should.be.eql(1); @@ -102,7 +100,7 @@ describe('UPDATE PriceConfig version', () => { it('should return 403 for member', (done) => { request(server) - .patch('/v4/projects/metadata/priceConfig/dev/versions/1') + .patch('/v5/projects/metadata/priceConfig/dev/versions/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/productCategories/create.js b/src/routes/productCategories/create.js index e80bc596..65c9413e 100644 --- a/src/routes/productCategories/create.js +++ b/src/routes/productCategories/create.js @@ -5,56 +5,58 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; const schema = { - body: { - param: Joi.object().keys({ - key: Joi.string().max(45).required(), - displayName: Joi.string().max(255).required(), - icon: Joi.string().max(255).required(), - question: Joi.string().max(255).required(), - info: Joi.string().max(255).required(), - aliases: Joi.array().required(), - disabled: Joi.boolean().optional(), - hidden: Joi.boolean().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + key: Joi.string().max(45).required(), + displayName: Joi.string().max(255).required(), + icon: Joi.string().max(255).required(), + question: Joi.string().max(255).required(), + info: Joi.string().max(255).required(), + aliases: Joi.array().required(), + disabled: Joi.boolean().optional(), + hidden: Joi.boolean().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ validate(schema), permissions('productCategory.create'), (req, res, next) => { - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { createdBy: req.authUser.userId, updatedBy: req.authUser.userId, }); // Check if duplicated key - return models.ProductCategory.findById(req.body.param.key) + return models.ProductCategory.findByPk(req.body.key) .then((existing) => { if (existing) { const apiErr = new Error(`Product category already exists for key ${req.params.key}`); - apiErr.status = 422; + apiErr.status = 400; return Promise.reject(apiErr); } // Create return models.ProductCategory.create(entity); }).then((createdEntity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PRODUCT_CATEGORY, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next); }, diff --git a/src/routes/productCategories/create.spec.js b/src/routes/productCategories/create.spec.js index c345eda3..eeb4c75a 100644 --- a/src/routes/productCategories/create.spec.js +++ b/src/routes/productCategories/create.spec.js @@ -12,46 +12,47 @@ import models from '../../models'; const should = chai.should(); describe('CREATE product category', () => { - beforeEach(() => testUtil.clearDb() - .then(() => models.ProductCategory.create({ - key: 'key1', - displayName: 'displayName 1', - icon: 'http://example.com/icon1.ico', - question: 'question 1', - info: 'info 1', - aliases: ['key-1', 'key_1'], - disabled: false, - hidden: false, - createdBy: 1, - updatedBy: 1, - })).then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProductCategory.create({ + key: 'key1', + displayName: 'displayName 1', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + disabled: false, + hidden: false, + createdBy: 1, + updatedBy: 1, + }).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /projects/metadata/productCategories', () => { const body = { - param: { - key: 'app_dev', - displayName: 'Application Development', - icon: 'prod-cat-app-icon', - info: 'Application Development Info', - question: 'What kind of devlopment you need?', - aliases: ['key-1', 'key_1'], - disabled: true, - hidden: true, - }, + key: 'app_dev', + displayName: 'Application Development', + icon: 'prod-cat-app-icon', + info: 'Application Development Info', + question: 'What kind of devlopment you need?', + aliases: ['key-1', 'key_1'], + disabled: true, + hidden: true, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -61,7 +62,7 @@ describe('CREATE product category', () => { it('should return 403 for copilot', (done) => { request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -71,7 +72,7 @@ describe('CREATE product category', () => { it('should return 403 for manager', (done) => { request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -79,93 +80,93 @@ describe('CREATE product category', () => { .expect(403, done); }); - it('should return 422 for missing key', (done) => { + it('should return 400 for missing key', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.key; + delete invalidBody.key; request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing displayName', (done) => { + it('should return 400 for missing displayName', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.displayName; + delete invalidBody.displayName; request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing icon', (done) => { + it('should return 400 for missing icon', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.icon; + delete invalidBody.icon; request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing question', (done) => { + it('should return 400 for missing question', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.question; + delete invalidBody.question; request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing info', (done) => { + it('should return 400 for missing info', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.info; + delete invalidBody.info; request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for duplicated key', (done) => { + it('should return 400 for duplicated key', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.key = 'key1'; + invalidBody.key = 'key1'; request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -173,15 +174,15 @@ describe('CREATE product category', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; - resJson.key.should.be.eql(body.param.key); - resJson.displayName.should.be.eql(body.param.displayName); - resJson.icon.should.be.eql(body.param.icon); - resJson.info.should.be.eql(body.param.info); - resJson.question.should.be.eql(body.param.question); - resJson.aliases.should.be.eql(body.param.aliases); - resJson.disabled.should.be.eql(body.param.disabled); - resJson.hidden.should.be.eql(body.param.hidden); + const resJson = res.body; + resJson.key.should.be.eql(body.key); + resJson.displayName.should.be.eql(body.displayName); + resJson.icon.should.be.eql(body.icon); + resJson.info.should.be.eql(body.info); + resJson.question.should.be.eql(body.question); + resJson.aliases.should.be.eql(body.aliases); + resJson.disabled.should.be.eql(body.disabled); + resJson.hidden.should.be.eql(body.hidden); resJson.createdBy.should.be.eql(40051333); // admin should.exist(resJson.createdAt); @@ -196,7 +197,7 @@ describe('CREATE product category', () => { it('should return 201 for connect admin', (done) => { request(server) - .post('/v4/projects/metadata/productCategories') + .post('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -204,15 +205,15 @@ describe('CREATE product category', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; - resJson.key.should.be.eql(body.param.key); - resJson.displayName.should.be.eql(body.param.displayName); - resJson.icon.should.be.eql(body.param.icon); - resJson.info.should.be.eql(body.param.info); - resJson.question.should.be.eql(body.param.question); - resJson.aliases.should.be.eql(body.param.aliases); - resJson.disabled.should.be.eql(body.param.disabled); - resJson.hidden.should.be.eql(body.param.hidden); + const resJson = res.body; + resJson.key.should.be.eql(body.key); + resJson.displayName.should.be.eql(body.displayName); + resJson.icon.should.be.eql(body.icon); + resJson.info.should.be.eql(body.info); + resJson.question.should.be.eql(body.question); + resJson.aliases.should.be.eql(body.aliases); + resJson.disabled.should.be.eql(body.disabled); + resJson.hidden.should.be.eql(body.hidden); resJson.createdBy.should.be.eql(40051336); // connect admin resJson.updatedBy.should.be.eql(40051336); // connect admin done(); diff --git a/src/routes/productCategories/delete.js b/src/routes/productCategories/delete.js index 89f575bd..c5a9f084 100644 --- a/src/routes/productCategories/delete.js +++ b/src/routes/productCategories/delete.js @@ -2,8 +2,11 @@ * API to delete a product category */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; +import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; @@ -19,7 +22,7 @@ module.exports = [ permissions('productCategory.delete'), (req, res, next) => models.sequelize.transaction(() => - models.ProductCategory.findById(req.params.key) + models.ProductCategory.findByPk(req.params.key) .then((entity) => { if (!entity) { const apiErr = new Error(`Product category not found for key ${req.params.key}`); @@ -30,7 +33,11 @@ module.exports = [ return entity.update({ deletedBy: req.authUser.userId }); }) .then(entity => entity.destroy())) - .then(() => { + .then((entity) => { + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.PRODUCT_CATEGORY, + _.pick(entity.toJSON(), 'key')); res.status(204).end(); }) .catch(next), diff --git a/src/routes/productCategories/delete.spec.js b/src/routes/productCategories/delete.spec.js index 40f8baa5..d5ada904 100644 --- a/src/routes/productCategories/delete.spec.js +++ b/src/routes/productCategories/delete.spec.js @@ -26,7 +26,7 @@ const expectAfterDelete = (key, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/projects/metadata/productCategories/${key}`) + .get(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -38,30 +38,33 @@ const expectAfterDelete = (key, err, next) => { describe('DELETE product category', () => { const key = 'key1'; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProductCategory.create({ - key: 'key1', - displayName: 'displayName 1', - icon: 'http://example.com/icon1.ico', - question: 'question 1', - info: 'info 1', - aliases: ['key-1', 'key_1'], - createdBy: 1, - updatedBy: 1, - })).then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProductCategory.create({ + key: 'key1', + displayName: 'displayName 1', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + createdBy: 1, + updatedBy: 1, + }).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/productCategories/{key}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete(`/v4/projects/metadata/productCategories/${key}`) + .delete(`/v5/projects/metadata/productCategories/${key}`) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete(`/v4/projects/metadata/productCategories/${key}`) + .delete(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -70,7 +73,7 @@ describe('DELETE product category', () => { it('should return 403 for copilot', (done) => { request(server) - .delete(`/v4/projects/metadata/productCategories/${key}`) + .delete(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -79,7 +82,7 @@ describe('DELETE product category', () => { it('should return 403 for manager', (done) => { request(server) - .delete(`/v4/projects/metadata/productCategories/${key}`) + .delete(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -88,7 +91,7 @@ describe('DELETE product category', () => { it('should return 404 for non-existed product category', (done) => { request(server) - .delete('/v4/projects/metadata/productCategories/not_existed') + .delete('/v5/projects/metadata/productCategories/not_existed') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -99,7 +102,7 @@ describe('DELETE product category', () => { models.ProductCategory.destroy({ where: { key } }) .then(() => { request(server) - .delete(`/v4/projects/metadata/productCategories/${key}`) + .delete(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -109,7 +112,7 @@ describe('DELETE product category', () => { it('should return 204, for admin, if the product category was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/productCategories/${key}`) + .delete(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -119,7 +122,7 @@ describe('DELETE product category', () => { it('should return 204, for connect admin, if the product category was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/productCategories/${key}`) + .delete(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/productCategories/get.js b/src/routes/productCategories/get.js index f113859b..6d0e19da 100644 --- a/src/routes/productCategories/get.js +++ b/src/routes/productCategories/get.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import models from '../../models'; +import util from '../../util'; const permissions = tcMiddleware.permissions; @@ -18,22 +18,44 @@ const schema = { module.exports = [ validate(schema), permissions('productCategory.view'), - (req, res, next) => models.ProductCategory.findOne({ - where: { - key: req.params.key, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((productCategory) => { - // Not found - if (!productCategory) { - const apiErr = new Error(`Product category not found for key ${req.params.key}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => { + util.fetchByIdFromES('productCategories', { + query: { + nested: { + path: 'productCategories', + query: { + match: { 'productCategories.key': req.params.key }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No productCategory found in ES'); + models.ProductCategory.findOne({ + where: { + key: req.params.key, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((productCategory) => { + // Not found + if (!productCategory) { + const apiErr = new Error(`Product category not found for key ${req.params.key}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, productCategory)); - return Promise.resolve(); + res.json(productCategory); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('productCategories found in ES'); + res.json(data[0].inner_hits.productCategories.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } }) - .catch(next), + .catch(next); + }, ]; diff --git a/src/routes/productCategories/get.spec.js b/src/routes/productCategories/get.spec.js index 87cf71cb..0e7e82bc 100644 --- a/src/routes/productCategories/get.spec.js +++ b/src/routes/productCategories/get.spec.js @@ -26,16 +26,18 @@ describe('GET product category', () => { const key = productCategory.key; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProductCategory.create(productCategory)) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProductCategory.create(productCategory).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/productCategories/{key}', () => { it('should return 404 for non-existed product category', (done) => { request(server) - .get('/v4/projects/metadata/productCategories/1234') + .get('/v5/projects/metadata/productCategories/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -46,7 +48,7 @@ describe('GET product category', () => { models.ProductCategory.destroy({ where: { key } }) .then(() => { request(server) - .get(`/v4/projects/metadata/productCategories/${key}`) + .get(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -56,13 +58,13 @@ describe('GET product category', () => { it('should return 200 for admin', (done) => { request(server) - .get(`/v4/projects/metadata/productCategories/${key}`) + .get(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(productCategory.key); resJson.displayName.should.be.eql(productCategory.displayName); resJson.icon.should.be.eql(productCategory.icon); @@ -84,13 +86,13 @@ describe('GET product category', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get(`/v4/projects/metadata/productCategories/${key}`) + .get(`/v5/projects/metadata/productCategories/${key}`) .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get(`/v4/projects/metadata/productCategories/${key}`) + .get(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -100,7 +102,7 @@ describe('GET product category', () => { it('should return 200 for connect manager', (done) => { request(server) - .get(`/v4/projects/metadata/productCategories/${key}`) + .get(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -110,7 +112,7 @@ describe('GET product category', () => { it('should return 200 for member', (done) => { request(server) - .get(`/v4/projects/metadata/productCategories/${key}`) + .get(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -119,7 +121,7 @@ describe('GET product category', () => { it('should return 200 for copilot', (done) => { request(server) - .get(`/v4/projects/metadata/productCategories/${key}`) + .get(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/productCategories/list.js b/src/routes/productCategories/list.js index abc2a9e7..4a5553c9 100644 --- a/src/routes/productCategories/list.js +++ b/src/routes/productCategories/list.js @@ -2,19 +2,30 @@ * API to list all product categories */ import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import models from '../../models'; +import util from '../../util'; const permissions = tcMiddleware.permissions; module.exports = [ permissions('productCategory.view'), - (req, res, next) => models.ProductCategory.findAll({ - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((productCategories) => { - res.json(util.wrapResponse(req.id, productCategories)); - }) - .catch(next), + (req, res, next) => { + util.fetchFromES('productCategories') + .then((data) => { + if (data.productCategories.length === 0) { + req.log.debug('No productCategory found in ES'); + models.ProductCategory.findAll({ + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((productCategories) => { + res.json(productCategories); + }) + .catch(next); + } else { + req.log.debug('productCategories found in ES'); + res.json(data.productCategories); + } + }); + }, ]; diff --git a/src/routes/productCategories/list.spec.js b/src/routes/productCategories/list.spec.js index 94114813..8bb216e5 100644 --- a/src/routes/productCategories/list.spec.js +++ b/src/routes/productCategories/list.spec.js @@ -38,17 +38,19 @@ describe('LIST product categories', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProductCategory.create(productCategories[0])) - .then(() => models.ProductCategory.create(productCategories[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProductCategory.create(productCategories[0])) + .then(() => models.ProductCategory.create(productCategories[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/productCategories', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/productCategories') + .get('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -56,7 +58,7 @@ describe('LIST product categories', () => { .end((err, res) => { const type = productCategories[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].key.should.be.eql(type.key); resJson[0].displayName.should.be.eql(type.displayName); @@ -79,13 +81,13 @@ describe('LIST product categories', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/productCategories') + .get('/v5/projects/metadata/productCategories') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/productCategories') + .get('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -95,7 +97,7 @@ describe('LIST product categories', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/productCategories') + .get('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -105,7 +107,7 @@ describe('LIST product categories', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/productCategories') + .get('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -114,7 +116,7 @@ describe('LIST product categories', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/productCategories') + .get('/v5/projects/metadata/productCategories') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/productCategories/update.js b/src/routes/productCategories/update.js index 68958966..c1eceb91 100644 --- a/src/routes/productCategories/update.js +++ b/src/routes/productCategories/update.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import util from '../../util'; import models from '../../models'; @@ -14,31 +15,31 @@ const schema = { params: { key: Joi.string().max(45).required(), }, - body: { - param: Joi.object().keys({ - key: Joi.any().strip(), - displayName: Joi.string().max(255).optional(), - icon: Joi.string().max(255).optional(), - question: Joi.string().max(255).optional(), - info: Joi.string().max(255).optional(), - aliases: Joi.array().optional(), - disabled: Joi.boolean().optional(), - hidden: Joi.boolean().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + + body: Joi.object().keys({ + key: Joi.any().strip(), + displayName: Joi.string().max(255).optional(), + icon: Joi.string().max(255).optional(), + question: Joi.string().max(255).optional(), + info: Joi.string().max(255).optional(), + aliases: Joi.array().optional(), + disabled: Joi.boolean().optional(), + hidden: Joi.boolean().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), + }; module.exports = [ validate(schema), permissions('productCategory.edit'), (req, res, next) => { - const entityToUpdate = _.assign(req.body.param, { + const entityToUpdate = _.assign(req.body, { updatedBy: req.authUser.userId, }); @@ -59,7 +60,11 @@ module.exports = [ return productCategory.update(entityToUpdate); }) .then((productCategory) => { - res.json(util.wrapResponse(req.id, productCategory)); + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, + RESOURCES.PRODUCT_CATEGORY, + entityToUpdate); + res.json(productCategory); return Promise.resolve(); }) .catch(next); diff --git a/src/routes/productCategories/update.spec.js b/src/routes/productCategories/update.spec.js index ad9a43b5..95a04ad9 100644 --- a/src/routes/productCategories/update.spec.js +++ b/src/routes/productCategories/update.spec.js @@ -26,35 +26,35 @@ describe('UPDATE product category', () => { }; const key = productCategory.key; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProductCategory.create(productCategory)) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProductCategory.create(productCategory).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('PATCH /projects/metadata/productCategories/{key}', () => { const body = { - param: { - displayName: 'displayName 1 - update', - icon: 'http://example.com/icon1.ico - update', - question: 'question 1 - update', - info: 'info 1 - update', - aliases: ['key-1-updated', 'key_1_updated'], - disabled: true, - hidden: true, - }, + displayName: 'displayName 1 - update', + icon: 'http://example.com/icon1.ico - update', + question: 'question 1 - update', + info: 'info 1 - update', + aliases: ['key-1-updated', 'key_1_updated'], + disabled: true, + hidden: true, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -64,7 +64,7 @@ describe('UPDATE product category', () => { it('should return 403 for copilot', (done) => { request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, @@ -74,7 +74,7 @@ describe('UPDATE product category', () => { it('should return 403 for manager', (done) => { request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, @@ -84,7 +84,7 @@ describe('UPDATE product category', () => { it('should return 404 for non-existed product category', (done) => { request(server) - .patch('/v4/projects/metadata/productCategories/1234') + .patch('/v5/projects/metadata/productCategories/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -96,7 +96,7 @@ describe('UPDATE product category', () => { models.ProductCategory.destroy({ where: { key } }) .then(() => { request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -107,23 +107,23 @@ describe('UPDATE product category', () => { it('should return 200 for admin displayName updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.question; - delete partialBody.param.aliases; - delete partialBody.param.disabled; - delete partialBody.param.hidden; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.question; + delete partialBody.aliases; + delete partialBody.disabled; + delete partialBody.hidden; request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); - resJson.displayName.should.be.eql(partialBody.param.displayName); + resJson.displayName.should.be.eql(partialBody.displayName); resJson.icon.should.be.eql(productCategory.icon); resJson.info.should.be.eql(productCategory.info); resJson.question.should.be.eql(productCategory.question); @@ -143,24 +143,24 @@ describe('UPDATE product category', () => { it('should return 200 for admin icon updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.info; - delete partialBody.param.displayName; - delete partialBody.param.question; - delete partialBody.param.aliases; - delete partialBody.param.disabled; - delete partialBody.param.hidden; + delete partialBody.info; + delete partialBody.displayName; + delete partialBody.question; + delete partialBody.aliases; + delete partialBody.disabled; + delete partialBody.hidden; request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(productCategory.displayName); - resJson.icon.should.be.eql(partialBody.param.icon); + resJson.icon.should.be.eql(partialBody.icon); resJson.info.should.be.eql(productCategory.info); resJson.question.should.be.eql(productCategory.question); resJson.aliases.should.be.eql(productCategory.aliases); @@ -179,25 +179,25 @@ describe('UPDATE product category', () => { it('should return 200 for admin info updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.displayName; - delete partialBody.param.question; - delete partialBody.param.aliases; - delete partialBody.param.disabled; - delete partialBody.param.hidden; + delete partialBody.icon; + delete partialBody.displayName; + delete partialBody.question; + delete partialBody.aliases; + delete partialBody.disabled; + delete partialBody.hidden; request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(productCategory.displayName); resJson.icon.should.be.eql(productCategory.icon); - resJson.info.should.be.eql(partialBody.param.info); + resJson.info.should.be.eql(partialBody.info); resJson.question.should.be.eql(productCategory.question); resJson.aliases.should.be.eql(productCategory.aliases); resJson.disabled.should.be.eql(productCategory.disabled); @@ -215,26 +215,26 @@ describe('UPDATE product category', () => { it('should return 200 for admin question updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.displayName; - delete partialBody.param.aliases; - delete partialBody.param.disabled; - delete partialBody.param.hidden; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.displayName; + delete partialBody.aliases; + delete partialBody.disabled; + delete partialBody.hidden; request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(productCategory.displayName); resJson.icon.should.be.eql(productCategory.icon); resJson.info.should.be.eql(productCategory.info); - resJson.question.should.be.eql(partialBody.param.question); + resJson.question.should.be.eql(partialBody.question); resJson.aliases.should.be.eql(productCategory.aliases); resJson.disabled.should.be.eql(productCategory.disabled); resJson.hidden.should.be.eql(productCategory.hidden); @@ -251,27 +251,27 @@ describe('UPDATE product category', () => { it('should return 200 for admin aliases updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.question; - delete partialBody.param.displayName; - delete partialBody.param.disabled; - delete partialBody.param.hidden; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.question; + delete partialBody.displayName; + delete partialBody.disabled; + delete partialBody.hidden; request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(productCategory.displayName); resJson.icon.should.be.eql(productCategory.icon); resJson.info.should.be.eql(productCategory.info); resJson.question.should.be.eql(productCategory.question); - resJson.aliases.should.be.eql(partialBody.param.aliases); + resJson.aliases.should.be.eql(partialBody.aliases); resJson.disabled.should.be.eql(productCategory.disabled); resJson.hidden.should.be.eql(productCategory.hidden); resJson.createdBy.should.be.eql(productCategory.createdBy); @@ -287,28 +287,28 @@ describe('UPDATE product category', () => { it('should return 200 for admin disabled updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.question; - delete partialBody.param.displayName; - delete partialBody.param.aliases; - delete partialBody.param.hidden; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.question; + delete partialBody.displayName; + delete partialBody.aliases; + delete partialBody.hidden; request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(productCategory.displayName); resJson.icon.should.be.eql(productCategory.icon); resJson.info.should.be.eql(productCategory.info); resJson.question.should.be.eql(productCategory.question); resJson.aliases.should.be.eql(productCategory.aliases); - resJson.disabled.should.be.eql(partialBody.param.disabled); + resJson.disabled.should.be.eql(partialBody.disabled); resJson.hidden.should.be.eql(productCategory.hidden); resJson.createdBy.should.be.eql(productCategory.createdBy); resJson.createdBy.should.be.eql(productCategory.createdBy); // should not update createdAt @@ -323,21 +323,21 @@ describe('UPDATE product category', () => { it('should return 200 for admin hidden updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.question; - delete partialBody.param.displayName; - delete partialBody.param.disabled; - delete partialBody.param.aliases; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.question; + delete partialBody.displayName; + delete partialBody.disabled; + delete partialBody.aliases; request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(productCategory.displayName); resJson.icon.should.be.eql(productCategory.icon); @@ -345,7 +345,7 @@ describe('UPDATE product category', () => { resJson.question.should.be.eql(productCategory.question); resJson.aliases.should.be.eql(productCategory.aliases); resJson.disabled.should.be.eql(productCategory.disabled); - resJson.hidden.should.be.eql(partialBody.param.hidden); + resJson.hidden.should.be.eql(partialBody.hidden); resJson.createdBy.should.be.eql(productCategory.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051333); // admin should.exist(resJson.updatedAt); @@ -358,22 +358,22 @@ describe('UPDATE product category', () => { it('should return 200 for admin all fields updated', (done) => { request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); - resJson.displayName.should.be.eql(body.param.displayName); - resJson.icon.should.be.eql(body.param.icon); - resJson.info.should.be.eql(body.param.info); - resJson.question.should.be.eql(body.param.question); - resJson.aliases.should.be.eql(body.param.aliases); - resJson.disabled.should.be.eql(body.param.disabled); - resJson.hidden.should.be.eql(body.param.hidden); + resJson.displayName.should.be.eql(body.displayName); + resJson.icon.should.be.eql(body.icon); + resJson.info.should.be.eql(body.info); + resJson.question.should.be.eql(body.question); + resJson.aliases.should.be.eql(body.aliases); + resJson.disabled.should.be.eql(body.disabled); + resJson.hidden.should.be.eql(body.hidden); resJson.createdBy.should.be.eql(productCategory.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051333); // admin should.exist(resJson.updatedAt); @@ -386,22 +386,22 @@ describe('UPDATE product category', () => { it('should return 200 for connect admin', (done) => { request(server) - .patch(`/v4/projects/metadata/productCategories/${key}`) + .patch(`/v5/projects/metadata/productCategories/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); - resJson.displayName.should.be.eql(body.param.displayName); - resJson.icon.should.be.eql(body.param.icon); - resJson.info.should.be.eql(body.param.info); - resJson.question.should.be.eql(body.param.question); - resJson.aliases.should.be.eql(body.param.aliases); - resJson.disabled.should.be.eql(body.param.disabled); - resJson.hidden.should.be.eql(body.param.hidden); + resJson.displayName.should.be.eql(body.displayName); + resJson.icon.should.be.eql(body.icon); + resJson.info.should.be.eql(body.info); + resJson.question.should.be.eql(body.question); + resJson.aliases.should.be.eql(body.aliases); + resJson.disabled.should.be.eql(body.disabled); + resJson.hidden.should.be.eql(body.hidden); resJson.createdBy.should.be.eql(productCategory.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051336); // connect admin done(); diff --git a/src/routes/productTemplates/create.js b/src/routes/productTemplates/create.js index 8875aa55..0dfcc4d1 100644 --- a/src/routes/productTemplates/create.js +++ b/src/routes/productTemplates/create.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import fieldLookupValidation from '../../middlewares/fieldLookupValidation'; import util from '../../util'; import models from '../../models'; @@ -12,43 +13,42 @@ import models from '../../models'; const permissions = tcMiddleware.permissions; const schema = { - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - category: Joi.string().max(45).required(), - subCategory: Joi.string().max(45).required(), - name: Joi.string().max(255).required(), - productKey: Joi.string().max(45).required(), - icon: Joi.string().max(255).required(), - brief: Joi.string().max(45).required(), - details: Joi.string().max(255).required(), - aliases: Joi.array().required(), - template: Joi.object().empty(null), - form: Joi.object().keys({ - key: Joi.string().required(), - version: Joi.number(), - }).empty(null), - disabled: Joi.boolean().optional(), - hidden: Joi.boolean().optional(), - isAddOn: Joi.boolean().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }) + body: Joi.object().keys({ + id: Joi.any().strip(), + category: Joi.string().max(45).required(), + subCategory: Joi.string().max(45).required(), + name: Joi.string().max(255).required(), + productKey: Joi.string().max(45).required(), + icon: Joi.string().max(255).required(), + brief: Joi.string().max(45).required(), + details: Joi.string().max(255).required(), + aliases: Joi.array().required(), + template: Joi.object().empty(null), + form: Joi.object().keys({ + key: Joi.string().required(), + version: Joi.number(), + }).empty(null), + disabled: Joi.boolean().optional(), + hidden: Joi.boolean().optional(), + isAddOn: Joi.boolean().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }) .xor('form', 'template') .required(), - }, + }; module.exports = [ validate(schema), permissions('productTemplate.create'), - fieldLookupValidation(models.ProductCategory, 'key', 'body.param.category', 'Category'), + fieldLookupValidation(models.ProductCategory, 'key', 'body.category', 'Category'), (req, res, next) => { - const param = req.body.param; + const param = req.body; const { form } = param; return util.checkModel(form, 'Form', models.Form, 'product template') .then(() => { @@ -59,9 +59,13 @@ module.exports = [ return models.ProductTemplate.create(entity) .then((createdEntity) => { + // emit event + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PRODUCT_TEMPLATE, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next); }) diff --git a/src/routes/productTemplates/create.spec.js b/src/routes/productTemplates/create.spec.js index 14f3807d..b0a99948 100644 --- a/src/routes/productTemplates/create.spec.js +++ b/src/routes/productTemplates/create.spec.js @@ -48,75 +48,75 @@ describe('CREATE product template', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProductCategory.bulkCreate(productCategories)) - .then(() => models.Form.create(forms[0])) - .then(() => models.Form.create(forms[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProductCategory.bulkCreate(productCategories)) + .then(() => models.Form.create(forms[0])) + .then(() => models.Form.create(forms[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /projects/metadata/productTemplates', () => { const body = { - param: { - name: 'name 1', - productKey: 'productKey 1', - category: 'generic', - subCategory: 'generic', - icon: 'http://example.com/icon1.ico', - brief: 'brief 1', - details: 'details 1', - aliases: ['product key 1', 'product_key_1'], - disabled: true, - hidden: true, - isAddOn: true, - template: { - template1: { - name: 'template 1', - details: { - anyDetails: 'any details 1', - }, - others: ['others 11', 'others 12'], + name: 'name 1', + productKey: 'productKey 1', + category: 'generic', + subCategory: 'generic', + icon: 'http://example.com/icon1.ico', + brief: 'brief 1', + details: 'details 1', + aliases: ['product key 1', 'product_key_1'], + disabled: true, + hidden: true, + isAddOn: true, + template: { + template1: { + name: 'template 1', + details: { + anyDetails: 'any details 1', }, - template2: { - name: 'template 2', - details: { - anyDetails: 'any details 2', - }, - others: ['others 21', 'others 22'], + others: ['others 11', 'others 12'], + }, + template2: { + name: 'template 2', + details: { + anyDetails: 'any details 2', }, + others: ['others 21', 'others 22'], }, }, }; const bodyDefinedFormTemplate = _.cloneDeep(body); - bodyDefinedFormTemplate.param.form = { + bodyDefinedFormTemplate.form = { version: 1, key: 'dev', }; const bodyWithForm = _.cloneDeep(bodyDefinedFormTemplate); - delete bodyWithForm.param.template; + delete bodyWithForm.template; const bodyMissingFormTemplate = _.cloneDeep(bodyWithForm); - delete bodyMissingFormTemplate.param.form; + delete bodyMissingFormTemplate.form; const bodyInvalidForm = _.cloneDeep(body); - bodyInvalidForm.param.form = { + bodyInvalidForm.form = { version: 1, key: 'wrongKey', }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -126,7 +126,7 @@ describe('CREATE product template', () => { it('should return 403 for copilot', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -136,7 +136,7 @@ describe('CREATE product template', () => { it('should return 403 for connect manager', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -144,53 +144,51 @@ describe('CREATE product template', () => { .expect(403, done); }); - it('should return 422 if validations dont pass', (done) => { + it('should return 400 if validations dont pass', (done) => { const invalidBody = { - param: { - aliases: 'a', - template: 1, - }, + aliases: 'a', + template: 1, }; request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if product category is missing', (done) => { + it('should return 400 if product category is missing', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.category = null; + invalidBody.category = null; request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if product category does not exist', (done) => { + it('should return 400 if product category does not exist', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.category = 'not_exist'; + invalidBody.category = 'not_exist'; request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -198,16 +196,16 @@ describe('CREATE product template', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(body.param.name); - resJson.productKey.should.be.eql(body.param.productKey); - resJson.category.should.be.eql(body.param.category); - resJson.icon.should.be.eql(body.param.icon); - resJson.brief.should.be.eql(body.param.brief); - resJson.details.should.be.eql(body.param.details); - resJson.aliases.should.be.eql(body.param.aliases); - resJson.template.should.be.eql(body.param.template); + resJson.name.should.be.eql(body.name); + resJson.productKey.should.be.eql(body.productKey); + resJson.category.should.be.eql(body.category); + resJson.icon.should.be.eql(body.icon); + resJson.brief.should.be.eql(body.brief); + resJson.details.should.be.eql(body.details); + resJson.aliases.should.be.eql(body.aliases); + resJson.template.should.be.eql(body.template); resJson.disabled.should.be.eql(true); resJson.hidden.should.be.eql(true); @@ -224,7 +222,7 @@ describe('CREATE product template', () => { it('should return 201 for connect admin', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -232,7 +230,7 @@ describe('CREATE product template', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051336); // connect admin resJson.updatedBy.should.be.eql(40051336); // connect admin done(); @@ -241,7 +239,7 @@ describe('CREATE product template', () => { it('should return 201 with form data', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -249,16 +247,16 @@ describe('CREATE product template', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(bodyWithForm.param.name); - resJson.productKey.should.be.eql(bodyWithForm.param.productKey); - resJson.category.should.be.eql(bodyWithForm.param.category); - resJson.icon.should.be.eql(bodyWithForm.param.icon); - resJson.brief.should.be.eql(bodyWithForm.param.brief); - resJson.details.should.be.eql(bodyWithForm.param.details); - resJson.aliases.should.be.eql(bodyWithForm.param.aliases); - resJson.form.should.be.eql(bodyWithForm.param.form); + resJson.name.should.be.eql(bodyWithForm.name); + resJson.productKey.should.be.eql(bodyWithForm.productKey); + resJson.category.should.be.eql(bodyWithForm.category); + resJson.icon.should.be.eql(bodyWithForm.icon); + resJson.brief.should.be.eql(bodyWithForm.brief); + resJson.details.should.be.eql(bodyWithForm.details); + resJson.aliases.should.be.eql(bodyWithForm.aliases); + resJson.form.should.be.eql(bodyWithForm.form); resJson.disabled.should.be.eql(true); resJson.hidden.should.be.eql(true); @@ -273,36 +271,36 @@ describe('CREATE product template', () => { }); }); - it('should return 422 when form is invalid', (done) => { + it('should return 400 when form is invalid', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyInvalidForm) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if both form or template field are defined', (done) => { + it('should return 400 if both form or template field are defined', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyDefinedFormTemplate) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if both form or template field are missing', (done) => { + it('should return 400 if both form or template field are missing', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates') + .post('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyMissingFormTemplate) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); }); }); diff --git a/src/routes/productTemplates/delete.js b/src/routes/productTemplates/delete.js index 03ce1d3b..f702a33b 100644 --- a/src/routes/productTemplates/delete.js +++ b/src/routes/productTemplates/delete.js @@ -2,8 +2,11 @@ * API to delete a product template */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; +import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; @@ -19,7 +22,7 @@ module.exports = [ permissions('productTemplate.delete'), (req, res, next) => models.sequelize.transaction(() => - models.ProductTemplate.findById(req.params.templateId) + models.ProductTemplate.findByPk(req.params.templateId) .then((entity) => { if (!entity) { const apiErr = new Error(`Product template not found for template id ${req.params.templateId}`); @@ -30,7 +33,12 @@ module.exports = [ return entity.update({ deletedBy: req.authUser.userId }); }) .then(entity => entity.destroy())) - .then(() => { + .then((entity) => { + // emit event + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.PRODUCT_TEMPLATE, + _.pick(entity.toJSON(), 'id')); res.status(204).end(); }) .catch(next), diff --git a/src/routes/productTemplates/delete.spec.js b/src/routes/productTemplates/delete.spec.js index 5c4d177c..893653eb 100644 --- a/src/routes/productTemplates/delete.spec.js +++ b/src/routes/productTemplates/delete.spec.js @@ -25,7 +25,7 @@ const expectAfterDelete = (id, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/projects/metadata/productTemplates/${id}`) + .get(`/v5/projects/metadata/productTemplates/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -38,51 +38,54 @@ const expectAfterDelete = (id, err, next) => { describe('DELETE product template', () => { let templateId; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProductTemplate.create({ - name: 'name 1', - productKey: 'productKey 1', - category: 'generic', - subCategory: 'generic', - icon: 'http://example.com/icon1.ico', - brief: 'brief 1', - details: 'details 1', - aliases: ['product key 1', 'product_key_1'], - template: { - template1: { - name: 'template 1', - details: { - anyDetails: 'any details 1', + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProductTemplate.create({ + name: 'name 1', + productKey: 'productKey 1', + category: 'generic', + subCategory: 'generic', + icon: 'http://example.com/icon1.ico', + brief: 'brief 1', + details: 'details 1', + aliases: ['product key 1', 'product_key_1'], + template: { + template1: { + name: 'template 1', + details: { + anyDetails: 'any details 1', + }, + others: ['others 11', 'others 12'], }, - others: ['others 11', 'others 12'], - }, - template2: { - name: 'template 2', - details: { - anyDetails: 'any details 2', + template2: { + name: 'template 2', + details: { + anyDetails: 'any details 2', + }, + others: ['others 21', 'others 22'], }, - others: ['others 21', 'others 22'], }, - }, - createdBy: 1, - updatedBy: 2, - })).then((template) => { - templateId = template.id; - return Promise.resolve(); - }), - ); - after(testUtil.clearDb); + createdBy: 1, + updatedBy: 2, + }).then((template) => { + templateId = template.id; + done(); + })); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/productTemplates/{templateId}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete(`/v4/projects/metadata/productTemplates/${templateId}`) + .delete(`/v5/projects/metadata/productTemplates/${templateId}`) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete(`/v4/projects/metadata/productTemplates/${templateId}`) + .delete(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -91,7 +94,7 @@ describe('DELETE product template', () => { it('should return 403 for copilot', (done) => { request(server) - .delete(`/v4/projects/metadata/productTemplates/${templateId}`) + .delete(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -100,7 +103,7 @@ describe('DELETE product template', () => { it('should return 403 for connect manager', (done) => { request(server) - .delete(`/v4/projects/metadata/productTemplates/${templateId}`) + .delete(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -109,7 +112,7 @@ describe('DELETE product template', () => { it('should return 404 for non-existed template', (done) => { request(server) - .delete('/v4/projects/metadata/productTemplates/1234') + .delete('/v5/projects/metadata/productTemplates/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -120,7 +123,7 @@ describe('DELETE product template', () => { models.ProductTemplate.destroy({ where: { id: templateId } }) .then(() => { request(server) - .delete(`/v4/projects/metadata/productTemplates/${templateId}`) + .delete(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -130,7 +133,7 @@ describe('DELETE product template', () => { it('should return 204, for admin, if template was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/productTemplates/${templateId}`) + .delete(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -140,7 +143,7 @@ describe('DELETE product template', () => { it('should return 204, for connect admin, if template was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/productTemplates/${templateId}`) + .delete(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/productTemplates/get.js b/src/routes/productTemplates/get.js index e660f979..c29cc744 100644 --- a/src/routes/productTemplates/get.js +++ b/src/routes/productTemplates/get.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import models from '../../models'; +import util from '../../util'; const permissions = tcMiddleware.permissions; @@ -18,24 +18,46 @@ const schema = { module.exports = [ validate(schema), permissions('productTemplate.view'), - (req, res, next) => models.ProductTemplate.findOne({ - where: { - deletedAt: { $eq: null }, - id: req.params.templateId, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((productTemplate) => { - // Not found - if (!productTemplate) { - const apiErr = new Error(`Product template not found for product id ${req.params.templateId}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => { + util.fetchByIdFromES('productTemplates', { + query: { + nested: { + path: 'productTemplates', + query: { + match: { 'productTemplates.id': req.params.templateId }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No productTemplate found in ES'); + models.ProductTemplate.findOne({ + where: { + deletedAt: { $eq: null }, + id: req.params.templateId, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((productTemplate) => { + // Not found + if (!productTemplate) { + const apiErr = new Error(`Product template not found for product id ${req.params.templateId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, productTemplate)); - return Promise.resolve(); + res.json(productTemplate); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('productTemplates found in ES'); + res.json(data[0].inner_hits.productTemplates.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } }) - .catch(next), + .catch(next); + }, ]; diff --git a/src/routes/productTemplates/get.spec.js b/src/routes/productTemplates/get.spec.js index 0c380dc7..4c0933d0 100644 --- a/src/routes/productTemplates/get.spec.js +++ b/src/routes/productTemplates/get.spec.js @@ -48,19 +48,21 @@ describe('GET product template', () => { let templateId; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProductTemplate.create(template)) - .then((createdTemplate) => { - templateId = createdTemplate.id; - return Promise.resolve(); - }), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProductTemplate.create(template).then((createdTemplate) => { + templateId = createdTemplate.id; + done(); + })); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/productTemplates/{templateId}', () => { it('should return 404 for non-existed template', (done) => { request(server) - .get('/v4/projects/metadata/productTemplates/1234') + .get('/v5/projects/metadata/productTemplates/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -71,7 +73,7 @@ describe('GET product template', () => { models.ProductTemplate.destroy({ where: { id: templateId } }) .then(() => { request(server) - .get(`/v4/projects/metadata/productTemplates/${templateId}`) + .get(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -81,13 +83,13 @@ describe('GET product template', () => { it('should return 200 for admin', (done) => { request(server) - .get(`/v4/projects/metadata/productTemplates/${templateId}`) + .get(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(templateId); resJson.name.should.be.eql(template.name); resJson.productKey.should.be.eql(template.productKey); @@ -111,13 +113,13 @@ describe('GET product template', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get(`/v4/projects/metadata/productTemplates/${templateId}`) + .get(`/v5/projects/metadata/productTemplates/${templateId}`) .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get(`/v4/projects/metadata/productTemplates/${templateId}`) + .get(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -127,7 +129,7 @@ describe('GET product template', () => { it('should return 200 for connect manager', (done) => { request(server) - .get(`/v4/projects/metadata/productTemplates/${templateId}`) + .get(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -137,7 +139,7 @@ describe('GET product template', () => { it('should return 200 for member', (done) => { request(server) - .get(`/v4/projects/metadata/productTemplates/${templateId}`) + .get(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -146,7 +148,7 @@ describe('GET product template', () => { it('should return 200 for copilot', (done) => { request(server) - .get(`/v4/projects/metadata/productTemplates/${templateId}`) + .get(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/productTemplates/list.js b/src/routes/productTemplates/list.js index d34f7d3e..3e98ed77 100644 --- a/src/routes/productTemplates/list.js +++ b/src/routes/productTemplates/list.js @@ -10,22 +10,31 @@ const permissions = tcMiddleware.permissions; module.exports = [ permissions('productTemplate.view'), (req, res, next) => { - const filters = util.parseQueryFilter(req.query.filter); - if (!util.isValidFilter(filters, ['productKey'])) { - return util.handleError('Invalid filters', null, req, next); - } - const where = { deletedAt: { $eq: null } }; - if (filters.productKey) { - where.productKey = { $eq: filters.productKey }; - } - return models.ProductTemplate.findAll({ - where, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((productTemplates) => { - res.json(util.wrapResponse(req.id, productTemplates)); - }) - .catch(next); + util.fetchFromES('productTemplates') + .then((data) => { + const filters = util.parseQueryFilter(req.query.filter); + if (!util.isValidFilter(filters, ['productKey'])) { + util.handleError('Invalid filters', null, req, next); + } + const where = { deletedAt: { $eq: null } }; + if (filters.productKey) { + where.productKey = { $eq: filters.productKey }; + } + if (data.productTemplates.length === 0) { + req.log.debug('No productTemplate found in ES'); + models.ProductTemplate.findAll({ + where, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((productTemplates) => { + res.json(productTemplates); + }) + .catch(next); + } else { + req.log.debug('productTemplates found in ES'); + res.json(data.productTemplates); + } + }); }, ]; diff --git a/src/routes/productTemplates/list.spec.js b/src/routes/productTemplates/list.spec.js index d872be8e..f04fcfed 100644 --- a/src/routes/productTemplates/list.spec.js +++ b/src/routes/productTemplates/list.spec.js @@ -90,25 +90,28 @@ describe('LIST product templates', () => { let templateId; - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.ProductTemplate.create(templates[0])) .then((createdTemplate) => { templateId = createdTemplate.id; - return models.ProductTemplate.create(templates[1]); - }).then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + return models.ProductTemplate.create(templates[1]).then(() => done()); + }); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/productTemplates', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/productTemplates') + .get('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; validateProductTemplates(2, resJson, templates); resJson[0].id.should.be.eql(templateId); done(); @@ -117,19 +120,19 @@ describe('LIST product templates', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/productTemplates') + .get('/v5/projects/metadata/productTemplates') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/productTemplates') + .get('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; validateProductTemplates(2, resJson, templates); resJson[0].id.should.be.eql(templateId); done(); @@ -138,13 +141,13 @@ describe('LIST product templates', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/productTemplates') + .get('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; validateProductTemplates(2, resJson, templates); resJson[0].id.should.be.eql(templateId); done(); @@ -153,12 +156,12 @@ describe('LIST product templates', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/productTemplates') + .get('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; validateProductTemplates(2, resJson, templates); resJson[0].id.should.be.eql(templateId); done(); @@ -167,12 +170,12 @@ describe('LIST product templates', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/productTemplates') + .get('/v5/projects/metadata/productTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; validateProductTemplates(2, resJson, templates); resJson[0].id.should.be.eql(templateId); done(); @@ -181,13 +184,13 @@ describe('LIST product templates', () => { it('should return filtered templates', (done) => { request(server) - .get('/v4/projects/metadata/productTemplates?filter=productKey%3DproductKey-2') + .get('/v5/projects/metadata/productTemplates?filter=productKey%3DproductKey-2') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; validateProductTemplates(1, resJson, [templates[1]]); done(); }); diff --git a/src/routes/productTemplates/update.js b/src/routes/productTemplates/update.js index d3d2fc2e..78a4c2ec 100644 --- a/src/routes/productTemplates/update.js +++ b/src/routes/productTemplates/update.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import fieldLookupValidation from '../../middlewares/fieldLookupValidation'; import util from '../../util'; import models from '../../models'; @@ -15,47 +16,45 @@ const schema = { params: { templateId: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - name: Joi.string().max(255), - productKey: Joi.string().max(45), - category: Joi.string().max(45), - subCategory: Joi.string().max(45), - icon: Joi.string().max(255), - brief: Joi.string().max(45), - details: Joi.string().max(255), - aliases: Joi.array(), - template: Joi.object().empty(null), - form: Joi.object().keys({ - key: Joi.string().required(), - version: Joi.number(), - }).empty(null), - disabled: Joi.boolean().optional(), - hidden: Joi.boolean().optional(), - isAddOn: Joi.boolean().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }) + body: Joi.object().keys({ + id: Joi.any().strip(), + name: Joi.string().max(255), + productKey: Joi.string().max(45), + category: Joi.string().max(45), + subCategory: Joi.string().max(45), + icon: Joi.string().max(255), + brief: Joi.string().max(45), + details: Joi.string().max(255), + aliases: Joi.array(), + template: Joi.object().empty(null), + form: Joi.object().keys({ + key: Joi.string().required(), + version: Joi.number(), + }).empty(null), + disabled: Joi.boolean().optional(), + hidden: Joi.boolean().optional(), + isAddOn: Joi.boolean().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }) .xor('form', 'template') .required(), - }, }; module.exports = [ validate(schema), permissions('productTemplate.edit'), - fieldLookupValidation(models.ProductCategory, 'key', 'body.param.category', 'Category'), + fieldLookupValidation(models.ProductCategory, 'key', 'body.category', 'Category'), (req, res, next) => { - const param = req.body.param; + const param = req.body; const { form } = param; return util.checkModel(form, 'Form', models.Form, 'product template') .then(() => { - const entityToUpdate = _.assign(req.body.param, { + const entityToUpdate = _.assign(req.body, { updatedBy: req.authUser.userId, }); @@ -82,7 +81,12 @@ module.exports = [ return productTemplate.update(entityToUpdate); }) .then((productTemplate) => { - res.json(util.wrapResponse(req.id, productTemplate)); + // emit event + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, + RESOURCES.PRODUCT_TEMPLATE, + entityToUpdate); + res.json(productTemplate); return Promise.resolve(); }) .catch(next); diff --git a/src/routes/productTemplates/update.spec.js b/src/routes/productTemplates/update.spec.js index 633ca3e0..3526b1cd 100644 --- a/src/routes/productTemplates/update.spec.js +++ b/src/routes/productTemplates/update.spec.js @@ -69,7 +69,8 @@ describe('UPDATE product template', () => { let templateId; - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.Form.create(forms[0])) .then(() => models.Form.create(forms[1])) .then(() => models.ProductCategory.bulkCreate([ @@ -94,73 +95,72 @@ describe('UPDATE product template', () => { updatedBy: 1, }, ])) - .then(() => models.ProductTemplate.create(template)) - .then((createdTemplate) => { + .then(() => models.ProductTemplate.create(template).then((createdTemplate) => { templateId = createdTemplate.id; - return Promise.resolve(); - }), - ); - after(testUtil.clearDb); + done(); + })); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('PATCH /projects/metadata/productTemplates/{templateId}', () => { const body = { - param: { - name: 'template 1 - update', - productKey: 'productKey 1 - update', - category: 'concrete', - subCategory: 'concrete', - icon: 'http://example.com/icon1-update.ico', - brief: 'brief 1 - update', - details: 'details 1 - update', - aliases: ['productTemplate-1-update', 'productTemplate_1-update'], - template: { - template1: { - name: 'template 1 - update', - details: { - anyDetails: 'any details 1 - update', - newDetails: 'new', - }, - others: ['others new'], + name: 'template 1 - update', + productKey: 'productKey 1 - update', + category: 'concrete', + subCategory: 'concrete', + icon: 'http://example.com/icon1-update.ico', + brief: 'brief 1 - update', + details: 'details 1 - update', + aliases: ['productTemplate-1-update', 'productTemplate_1-update'], + template: { + template1: { + name: 'template 1 - update', + details: { + anyDetails: 'any details 1 - update', + newDetails: 'new', }, - template3: { - name: 'template 3', - details: { - anyDetails: 'any details 3', - }, - others: ['others 31', 'others 32'], + others: ['others new'], + }, + template3: { + name: 'template 3', + details: { + anyDetails: 'any details 3', }, + others: ['others 31', 'others 32'], }, }, }; const bodyDefinedFormTemplate = _.cloneDeep(body); - bodyDefinedFormTemplate.param.form = { + bodyDefinedFormTemplate.form = { version: 1, key: 'dev', }; const bodyWithForm = _.cloneDeep(bodyDefinedFormTemplate); - delete bodyWithForm.param.template; + delete bodyWithForm.template; const bodyMissingFormTemplate = _.cloneDeep(bodyWithForm); - delete bodyMissingFormTemplate.param.form; + delete bodyMissingFormTemplate.form; const bodyInvalidForm = _.cloneDeep(body); - bodyInvalidForm.param.form = { + bodyInvalidForm.form = { version: 1, key: 'wrongKey', }; it('should return 403 if user is not authenticated', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -170,7 +170,7 @@ describe('UPDATE product template', () => { it('should return 403 for copilot', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, @@ -180,7 +180,7 @@ describe('UPDATE product template', () => { it('should return 403 for connect manager', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, @@ -188,26 +188,24 @@ describe('UPDATE product template', () => { .expect(403, done); }); - it('should return 422 for invalid request', (done) => { + it('should return 400 for invalid request', (done) => { const invalidBody = { - param: { - aliases: 'a', - template: 1, - }, + aliases: 'a', + template: 1, }; request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) - .expect(422, done); + .expect(400, done); }); it('should return 404 for non-existed template', (done) => { request(server) - .patch('/v4/projects/metadata/productTemplates/1234') + .patch('/v5/projects/metadata/productTemplates/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -219,7 +217,7 @@ describe('UPDATE product template', () => { models.ProductTemplate.destroy({ where: { id: templateId } }) .then(() => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -230,24 +228,24 @@ describe('UPDATE product template', () => { it('should return 200 for admin', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(templateId); - resJson.name.should.be.eql(body.param.name); - resJson.productKey.should.be.eql(body.param.productKey); - resJson.category.should.be.eql(body.param.category); - resJson.icon.should.be.eql(body.param.icon); - resJson.brief.should.be.eql(body.param.brief); - resJson.details.should.be.eql(body.param.details); + resJson.name.should.be.eql(body.name); + resJson.productKey.should.be.eql(body.productKey); + resJson.category.should.be.eql(body.category); + resJson.icon.should.be.eql(body.icon); + resJson.brief.should.be.eql(body.brief); + resJson.details.should.be.eql(body.details); resJson.disabled.should.be.eql(true); resJson.hidden.should.be.eql(true); - resJson.aliases.should.be.eql(body.param.aliases); + resJson.aliases.should.be.eql(body.aliases); resJson.template.should.be.eql({ template1: { name: 'template 1 - update', @@ -285,7 +283,7 @@ describe('UPDATE product template', () => { it('should return 200 for connect admin', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -296,47 +294,47 @@ describe('UPDATE product template', () => { it('should return 200 when update form', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyWithForm) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; - resJson.form.should.be.eql(bodyWithForm.param.form); + const resJson = res.body; + resJson.form.should.be.eql(bodyWithForm.form); done(); }); }); - it('should return 422 when form is invalid', (done) => { + it('should return 400 when form is invalid', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyInvalidForm) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if both form or template field are defined', (done) => { + it('should return 400 if both form or template field are defined', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyDefinedFormTemplate) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if both form or template field are missing', (done) => { + it('should return 400 if both form or template field are missing', (done) => { request(server) - .patch(`/v4/projects/metadata/productTemplates/${templateId}`) + .patch(`/v5/projects/metadata/productTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyMissingFormTemplate) - .expect(422, done); + .expect(400, done); }); }); }); diff --git a/src/routes/productTemplates/upgrade.js b/src/routes/productTemplates/upgrade.js index c78fe827..4eade980 100644 --- a/src/routes/productTemplates/upgrade.js +++ b/src/routes/productTemplates/upgrade.js @@ -5,20 +5,19 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; const schema = { - body: { - param: Joi.object().keys({ - form: Joi.object().keys({ - version: Joi.number().integer().positive().required(), - key: Joi.string().required(), - }).optional(), + body: Joi.object().keys({ + form: Joi.object().keys({ + version: Joi.number().integer().positive().required(), + key: Joi.string().required(), }).optional(), - }, + }).optional(), }; module.exports = [ @@ -32,19 +31,19 @@ module.exports = [ }, }).then(async (productTemplate) => { if (_.isNil(productTemplate)) { - const apiErr = new Error(`product template not found for id ${req.body.param.templateId}`); + const apiErr = new Error(`product template not found for id ${req.body.templateId}`); apiErr.status = 404; throw apiErr; } if (_.isNil(productTemplate.template)) { const apiErr = new Error('Current product template\'s template is null'); - apiErr.status = 422; + apiErr.status = 400; throw apiErr; } let newForm = {}; - if (_.isNil(req.body.param.form)) { + if (_.isNil(req.body.form)) { const { productKey, template = {} } = productTemplate; const { version } = await models.Form.createNewVersion(productKey, template, req.authUser.userId); newForm = { @@ -52,7 +51,7 @@ module.exports = [ key: productKey, }; } else { - newForm = req.body.param.form; + newForm = req.body.form; await util.checkModel(newForm, 'Form', models.Form, 'product template'); } // update product template with new form data @@ -63,13 +62,14 @@ module.exports = [ }; const newProductTemplate = await productTemplate.update(updatePayload); - const response = util.wrapResponse( - req.id, - _.omit(newProductTemplate.toJSON(), 'deletedAt', 'deletedBy'), - 1, - 201, - ); - return res.status(201).json(response); + + // emit event + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, + RESOURCES.PRODUCT_TEMPLATE, + updatePayload); + + return res.status(201).json(_.omit(newProductTemplate.toJSON(), 'deletedAt', 'deletedBy')); }).catch(next)); }, ]; diff --git a/src/routes/productTemplates/upgrade.spec.js b/src/routes/productTemplates/upgrade.spec.js index 0cdfa5d4..d3678541 100644 --- a/src/routes/productTemplates/upgrade.spec.js +++ b/src/routes/productTemplates/upgrade.spec.js @@ -60,7 +60,8 @@ describe('UPGRADE product template', () => { let templateId; let missingTemplateId; - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.ProductCategory.bulkCreate([ { key: 'generic', @@ -122,47 +123,45 @@ describe('UPGRADE product template', () => { .then((createdTemplate) => { templateId = createdTemplate.id; }) - .then(() => models.ProductTemplate.create(productTemplateMissed)) - .then((createdTemplate) => { + .then(() => models.ProductTemplate.create(productTemplateMissed).then((createdTemplate) => { missingTemplateId = createdTemplate.id; - }), - ); - after(testUtil.clearDb); + done(); + })); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /projects/metadata/productTemplates/{templateId}/upgrade', () => { const body = { - param: { - form: { - key: 'newKey', - version: 1, - }, + + form: { + key: 'newKey', + version: 1, }, + }; const bodyInvalidForm = { - param: { - form: { - key: 'wrongKey', - version: 1, - }, + form: { + key: 'wrongKey', + version: 1, }, }; const emptyBody = { - param: { - }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`) .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -172,7 +171,7 @@ describe('UPGRADE product template', () => { it('should return 403 for copilot', (done) => { request(server) - .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, @@ -182,7 +181,7 @@ describe('UPGRADE product template', () => { it('should return 403 for connect manager', (done) => { request(server) - .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, @@ -190,28 +189,26 @@ describe('UPGRADE product template', () => { .expect(403, done); }); - it('should return 422 for invalid request', (done) => { + it('should return 400 for invalid request', (done) => { const invalidBody = { - param: { - form: { - key: 'notvalid', - version: 1, - }, + form: { + key: 'notvalid', + version: 1, }, }; request(server) - .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) - .expect(422, done); + .expect(400, done); }); it('should return 404 for non-existed template', (done) => { request(server) - .post('/v4/projects/metadata/productTemplates/1234/upgrade') + .post('/v5/projects/metadata/productTemplates/1234/upgrade') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -223,7 +220,7 @@ describe('UPGRADE product template', () => { models.ProductTemplate.destroy({ where: { id: templateId } }) .then(() => { request(server) - .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -234,14 +231,14 @@ describe('UPGRADE product template', () => { it('should return 200 for admin', (done) => { request(server) - .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(templateId); should.not.exist(resJson.template); @@ -260,16 +257,16 @@ describe('UPGRADE product template', () => { }); }); - it('should create new version of model if param not given model key and version', (done) => { + it('should create new version of model if body not given model key and version', (done) => { request(server) - .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(emptyBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.not.exist(resJson.scope); should.not.exist(resJson.phases); @@ -290,24 +287,24 @@ describe('UPGRADE product template', () => { }); }); - it('should return 422 when form is invalid', (done) => { + it('should return 400 when form is invalid', (done) => { request(server) - .post(`/v4/projects/metadata/productTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyInvalidForm) - .expect(422, done); + .expect(400, done); }); - it('should return 422 when template is missing', (done) => { + it('should return 400 when template is missing', (done) => { request(server) - .post(`/v4/projects/metadata/productTemplates/${missingTemplateId}/upgrade`) + .post(`/v5/projects/metadata/productTemplates/${missingTemplateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) - .expect(422, done); + .expect(400, done); }); }); }); diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 46b70399..0385e04c 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -95,7 +95,6 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => { !_.find(existentUsers, existentUser => compareEmail(existentUser.email, inviteEmail, { UNIQUE_GMAIL_VALIDATION: false })), ); - // remove invites for users that are invited already _.remove(existentUsersWithNumberId, user => _.some(invites, i => i.userId === user.id)); existentUsersWithNumberId.forEach((user) => { @@ -103,10 +102,8 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => { dataNew.userId = user.id; dataNew.email = user.email; - invitePromises.push(models.ProjectMemberInvite.create(dataNew)); }); - // remove invites for users that are invited already _.remove(nonExistentUserEmails, email => _.some(invites, i => @@ -115,7 +112,6 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => { const dataNew = _.clone(data); dataNew.email = email; - invitePromises.push(models.ProjectMemberInvite.create(dataNew)); }); return invitePromises; @@ -125,7 +121,6 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => { return invitePromises; }); } - return invitePromises; }; @@ -133,7 +128,7 @@ const sendInviteEmail = (req, projectId, invite) => { req.log.debug(req.authUser); const emailEventType = BUS_API_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; const promises = [ - models.Project.find({ + models.Project.findOne({ where: { id: projectId }, raw: true, }), @@ -261,7 +256,8 @@ module.exports = [ }; req.log.debug('Creating invites'); - return models.sequelize.Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed)) + return models.Sequelize.Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed)) + // return Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed)) .then((values) => { values.forEach((v) => { req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, { diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index 4aae6cd5..8fb0a4a6 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -163,7 +163,7 @@ describe('Project Member Invite create', () => { it('should return 201 if userIds and emails are presented the same time', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -189,7 +189,7 @@ describe('Project Member Invite create', () => { it('should return 400 if neither userIds or email is presented', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -204,7 +204,8 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - res.body.result.status.should.equal(400); + const errorMessage = _.get(res.body, 'message', ''); + sinon.assert.match(errorMessage, /.*Either userIds or emails are required/); done(); } }); @@ -229,7 +230,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -245,9 +246,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; - res.body.result.status.should.equal(403); - const errorMessage = _.get(resJson, 'message', ''); + const errorMessage = _.get(res.body, 'message', ''); sinon.assert.match(errorMessage, /.*You are not allowed to invite user as/); done(); } @@ -273,7 +272,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -289,9 +288,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; - res.body.result.status.should.equal(403); - const errorMessage = _.get(resJson, 'message', ''); + const errorMessage = _.get(res.body, 'message', ''); sinon.assert.match(errorMessage, /.*You are not allowed to invite user as/); done(); } @@ -319,7 +316,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -372,7 +369,7 @@ describe('Project Member Invite create', () => { email: 'hello@world.com', }])); request(server) - .post(`/v4/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -421,7 +418,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project2.id}/members/invite`) + .post(`/v5/projects/${project2.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -469,7 +466,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -525,7 +522,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -541,9 +538,8 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); - res.body.result.status.should.equal(403); const errorMessage = _.get(resJson, 'message', ''); sinon.assert.match(errorMessage, /.*not allowed to invite user as/); done(); @@ -555,7 +551,7 @@ describe('Project Member Invite create', () => { util.getUserRoles.restore(); sandbox.stub(util, 'getUserRoles', () => Promise.resolve([USER_ROLE.MANAGER])); request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -582,7 +578,7 @@ describe('Project Member Invite create', () => { util.getUserRoles.restore(); sandbox.stub(util, 'getUserRoles', () => Promise.resolve([USER_ROLE.MANAGER])); request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -609,7 +605,7 @@ describe('Project Member Invite create', () => { util.getUserRoles.restore(); sandbox.stub(util, 'getUserRoles', () => Promise.resolve(['Topcoder User'])); request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -656,7 +652,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -685,7 +681,7 @@ describe('Project Member Invite create', () => { it('should return 201 and empty response when trying add already invited member by lowercase email', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -711,7 +707,7 @@ describe('Project Member Invite create', () => { it('should return 201 and empty response when trying add already invited member by uppercase email', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -738,7 +734,7 @@ describe('Project Member Invite create', () => { xit('should return 201 and empty response when trying add already invited member by gmail email with dot', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -765,7 +761,7 @@ describe('Project Member Invite create', () => { xit('should return 201 and empty response when trying add already invited member by gmail email without dot', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -820,7 +816,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -867,7 +863,7 @@ describe('Project Member Invite create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) diff --git a/src/routes/projectMemberInvites/get.spec.js b/src/routes/projectMemberInvites/get.spec.js index b5a1757d..9d9afcad 100644 --- a/src/routes/projectMemberInvites/get.spec.js +++ b/src/routes/projectMemberInvites/get.spec.js @@ -76,13 +76,13 @@ describe('GET Project', () => { describe('GET /projects/{id}/members/invite', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get(`/v4/projects/${project2.id}/members/invite`) + .get(`/v5/projects/${project2.id}/members/invite`) .expect(403, done); }); it('should return 404 if requested project doesn\'t exist', (done) => { request(server) - .get('/v4/projects/14343323/members/invite') + .get('/v5/projects/14343323/members/invite') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -91,7 +91,7 @@ describe('GET Project', () => { it('should return the invite if user is invited to this project', (done) => { request(server) - .get(`/v4/projects/${project1.id}/members/invite`) + .get(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -113,7 +113,7 @@ describe('GET Project', () => { it('should return 404 if user is not invited to this project', (done) => { request(server) - .get(`/v4/projects/${project2.id}/members/invite`) + .get(`/v5/projects/${project2.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/projectMemberInvites/update.spec.js b/src/routes/projectMemberInvites/update.spec.js index 0383291b..47b83942 100644 --- a/src/routes/projectMemberInvites/update.spec.js +++ b/src/routes/projectMemberInvites/update.spec.js @@ -118,14 +118,14 @@ describe('Project member invite update', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/invite`) + .patch(`/v5/projects/${project1.id}/members/invite`) .send(body) .expect(403, done); }); it('should return 404 if user has no invite', (done) => { request(server) - .put(`/v4/projects/${project1.id}/members/invite`) + .put(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -144,7 +144,7 @@ describe('Project member invite update', () => { it('should return 400 no userId or email is presented', (done) => { request(server) - .put(`/v4/projects/${project1.id}/members/invite`) + .put(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -159,9 +159,8 @@ describe('Project member invite update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); - res.body.result.status.should.equal(400); const errorMessage = _.get(resJson, 'message', ''); sinon.assert.match(errorMessage, /.*userId or email should be provided/); done(); @@ -188,7 +187,7 @@ describe('Project member invite update', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .put(`/v4/projects/${project1.id}/members/invite`) + .put(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -204,9 +203,8 @@ describe('Project member invite update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); - res.body.result.status.should.equal(403); const errorMessage = _.get(resJson, 'message', ''); sinon.assert.match(errorMessage, /.*Project members can cancel invites only for customer/); done(); @@ -233,7 +231,7 @@ describe('Project member invite update', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .put(`/v4/projects/${project1.id}/members/invite`) + .put(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -249,9 +247,8 @@ describe('Project member invite update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); - res.body.result.status.should.equal(403); const errorMessage = _.get(resJson, 'message', ''); sinon.assert.match(errorMessage, /.*Project members can cancel invites only for customer/); done(); @@ -278,7 +275,7 @@ describe('Project member invite update', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .put(`/v4/projects/${project1.id}/members/invite`) + .put(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -294,9 +291,8 @@ describe('Project member invite update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); - res.body.result.status.should.equal(403); const errorMessage = _.get(resJson, 'message', ''); sinon.assert.match(errorMessage, 'Requested invites can only be updated by Copilot manager'); done(); @@ -336,7 +332,7 @@ describe('Project member invite update', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .put(`/v4/projects/${project1.id}/members/invite`) + .put(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -353,25 +349,13 @@ describe('Project member invite update', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledThrice.should.be.true; + createEventSpy.calledOnce.should.be.true; createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({ projectId: project1.id, userId: invite1.userId, status: INVITE_STATUS.ACCEPTED, email: null, })).should.be.true; - createEventSpy.secondCall.calledWith(BUS_API_EVENT.MEMBER_JOINED, sinon.match({ - projectId: project1.id, - projectName: project1.name, - userId: invite1.userId, - initiatorUserId: 40051331, - })).should.be.true; - createEventSpy.thirdCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ - projectId: project1.id, - projectName: project1.name, - userId: invite1.userId, - initiatorUserId: 40051331, - })).should.be.true; done(); }); } diff --git a/src/routes/projectMembers/create.spec.js b/src/routes/projectMembers/create.spec.js index f6c85633..82a04e5c 100644 --- a/src/routes/projectMembers/create.spec.js +++ b/src/routes/projectMembers/create.spec.js @@ -8,8 +8,7 @@ import models from '../../models'; import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; -import busApi from '../../services/busApi'; -import { USER_ROLE, BUS_API_EVENT } from '../../constants'; +import { USER_ROLE } from '../../constants'; const should = chai.should(); @@ -52,7 +51,7 @@ describe('Project Members create', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .post(`/v4/projects/${project1.id}/members/`) + .post(`/v5/projects/${project1.id}/members/`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -79,7 +78,7 @@ describe('Project Members create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project1.id}/members/invite`) + .post(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -101,9 +100,8 @@ describe('Project Members create', () => { resJson.projectId.should.equal(project1.id); resJson.userId.should.equal(40051332); server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; - request(server) - .put(`/v4/projects/${project1.id}/members/invite`) + .put(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -128,7 +126,7 @@ describe('Project Members create', () => { server.services.pubsub.publish.calledWith('project.member.added').should.be.true; request(server) - .put(`/v4/projects/${project1.id}/members/invite`) + .put(`/v5/projects/${project1.id}/members/invite`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -144,7 +142,8 @@ describe('Project Members create', () => { if (err3) { done(err3); } else { - res3.body.result.status.should.equal(404); + const errorMessage = _.get(res3.body, 'message', ''); + sinon.assert.match(errorMessage, /.*invite not found for project id 1, email undefined and userId/); done(); } }); @@ -185,7 +184,7 @@ describe('Project Members create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post(`/v4/projects/${project1.id}/members/`) + .post(`/v5/projects/${project1.id}/members/`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -206,128 +205,5 @@ describe('Project Members create', () => { } }); }); - - 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('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when manager added', (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.MANAGER, - }], - }, - }, - }), - post: () => Promise.resolve({ - status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: {}, - }, - }, - }), - }); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); - request(server) - .post(`/v4/projects/${project1.id}/members/`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .expect(201) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.MEMBER_JOINED_MANAGER); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ - projectId: project1.id, - projectName: project1.name, - projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, - userId: 40051334, - initiatorUserId: 40051334, - })).should.be.true; - done(); - }); - } - }); - }); - - it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when copilot added', (done) => { - request(server) - .post(`/v4/projects/${project1.id}/members/invite`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send({ - param: { - userIds: [40051332], - role: 'copilot', - }, - }) - .expect(201) - .end((err) => { - if (err) { - done(err); - } else { - request(server) - .put(`/v4/projects/${project1.id}/members/invite`) - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .send({ - param: { - userId: 40051332, - status: 'accepted', - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err2) => { - if (err2) { - done(err2); - } else { - testUtil.wait(() => { - createEventSpy.callCount.should.equal(4); - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REQUESTED); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED); - createEventSpy.thirdCall.calledWith(BUS_API_EVENT.MEMBER_JOINED_COPILOT); - createEventSpy.lastCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ - projectId: project1.id, - projectName: project1.name, - projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, - userId: 40051336, - initiatorUserId: 40051336, - })).should.be.true; - done(); - }); - } - }); - } - }); - }); - }); }); }); diff --git a/src/routes/projectMembers/delete.spec.js b/src/routes/projectMembers/delete.spec.js index 9f539ef2..80923a3f 100644 --- a/src/routes/projectMembers/delete.spec.js +++ b/src/routes/projectMembers/delete.spec.js @@ -93,7 +93,7 @@ describe('Project members delete', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/members/${member1.id}`) + .delete(`/v5/projects/${project1.id}/members/${member1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -109,7 +109,7 @@ describe('Project members delete', () => { it('should return 403 if user not found', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/members/8888888`) + .delete(`/v5/projects/${project1.id}/members/8888888`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -125,7 +125,7 @@ describe('Project members delete', () => { it('should return 204 if copilot user has access to the project', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/members/${member1.id}`) + .delete(`/v5/projects/${project1.id}/members/${member1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -184,7 +184,7 @@ describe('Project members delete', () => { updatedAt: '2016-08-30 00:33:07+00', }]).then(() => { request(server) - .delete(`/v4/projects/${project1.id}/members/${member1.id}`) + .delete(`/v5/projects/${project1.id}/members/${member1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -242,7 +242,7 @@ describe('Project members delete', () => { const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .delete(`/v4/projects/${project1.id}/members/${member2.id}`) + .delete(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -289,7 +289,7 @@ describe('Project members delete', () => { }) .then(() => { request(server) - .delete(`/v4/projects/${project1.id}/members/${member2.id}`) + .delete(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -313,7 +313,7 @@ describe('Project members delete', () => { it('should return 403 if copilot user is trying to remove a manager', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/members/${member2.id}`) + .delete(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -349,7 +349,7 @@ describe('Project members delete', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .delete(`/v4/projects/${project1.id}/members/${member2.id}`) + .delete(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -376,7 +376,7 @@ describe('Project members delete', () => { it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when copilot removed', (done) => { request(server) - .delete(`/v4/projects/${project1.id}/members/${member1.id}`) + .delete(`/v5/projects/${project1.id}/members/${member1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) diff --git a/src/routes/projectMembers/update.spec.js b/src/routes/projectMembers/update.spec.js index 5b39f2dc..69916d52 100644 --- a/src/routes/projectMembers/update.spec.js +++ b/src/routes/projectMembers/update.spec.js @@ -104,7 +104,7 @@ describe('Project members update', () => { it('should return 403 if user does not have permissions', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .patch(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -112,21 +112,21 @@ describe('Project members update', () => { .expect(403, done); }); - it('should return 422 if no role', (done) => { + it('should return 400 if no role', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .patch(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ param: {}, }) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if role is invalid', (done) => { + it('should return 400 if role is invalid', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .patch(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -135,12 +135,12 @@ describe('Project members update', () => { role: 'wrong', }, }) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if isPrimary is invalid', (done) => { + it('should return 400 if isPrimary is invalid', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .patch(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -149,12 +149,12 @@ describe('Project members update', () => { isPrimary: 'wrong', }, }) - .expect(422, done); + .expect(400, done); }); it('should return 404 if not exist id', (done) => { request(server) - .patch(`/v4/projects/${project1.id}/members/999999`) + .patch(`/v5/projects/${project1.id}/members/999999`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -165,10 +165,7 @@ describe('Project members update', () => { if (err) { done(err); } else { - const result = res.body.result; - result.success.should.be.false; - result.status.should.equal(404); - result.content.message.should.equal('project member not found for project id' + + res.body.message.should.equal('project member not found for project id' + ` ${project1.id} and member id 999999`); done(); } @@ -192,7 +189,7 @@ describe('Project members update', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .patch(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -243,7 +240,7 @@ describe('Project members update', () => { }) .then(() => { request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .patch(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -284,7 +281,7 @@ describe('Project members update', () => { const deleteSpy = sinon.spy(mockHttpClient, 'delete'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .patch(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -314,7 +311,7 @@ describe('Project members update', () => { const deleteSpy = sinon.spy(mockHttpClient, 'delete'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .patch(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -353,7 +350,7 @@ describe('Project members update', () => { const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member3.id}`) + .patch(`/v5/projects/${project1.id}/members/${member3.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -407,7 +404,7 @@ describe('Project members update', () => { const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member3.id}`) + .patch(`/v5/projects/${project1.id}/members/${member3.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -456,7 +453,7 @@ describe('Project members update', () => { const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member1.id}`) + .patch(`/v5/projects/${project1.id}/members/${member1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -513,7 +510,7 @@ describe('Project members update', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}/members/${member2.id}`) + .patch(`/v5/projects/${project1.id}/members/${member2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/projectTemplates/create.js b/src/routes/projectTemplates/create.js index 80cd520e..5cc7ae07 100644 --- a/src/routes/projectTemplates/create.js +++ b/src/routes/projectTemplates/create.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import fieldLookupValidation from '../../middlewares/fieldLookupValidation'; import util from '../../util'; import models from '../../models'; @@ -12,52 +13,50 @@ import models from '../../models'; const permissions = tcMiddleware.permissions; const schema = { - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - name: Joi.string().max(255).required(), - key: Joi.string().max(45).required(), - category: Joi.string().max(45).required(), - icon: Joi.string().max(255).required(), - question: Joi.string().max(255).required(), - info: Joi.string().max(255).required(), - aliases: Joi.array().required(), - scope: Joi.object().empty(null), - phases: Joi.object().empty(null), - form: Joi.object().keys({ - key: Joi.string().required(), - version: Joi.number(), - }).empty(null), - planConfig: Joi.object().keys({ - key: Joi.string().required(), - version: Joi.number(), - }).empty(null), - priceConfig: Joi.object().keys({ - key: Joi.string().required(), - version: Joi.number(), - }).empty(null), - disabled: Joi.boolean().optional(), - hidden: Joi.boolean().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }) + body: Joi.object().keys({ + id: Joi.any().strip(), + name: Joi.string().max(255).required(), + key: Joi.string().max(45).required(), + category: Joi.string().max(45).required(), + icon: Joi.string().max(255).required(), + question: Joi.string().max(255).required(), + info: Joi.string().max(255).required(), + aliases: Joi.array().required(), + scope: Joi.object().empty(null), + phases: Joi.object().empty(null), + form: Joi.object().keys({ + key: Joi.string().required(), + version: Joi.number(), + }).empty(null), + planConfig: Joi.object().keys({ + key: Joi.string().required(), + version: Joi.number(), + }).empty(null), + priceConfig: Joi.object().keys({ + key: Joi.string().required(), + version: Joi.number(), + }).empty(null), + disabled: Joi.boolean().optional(), + hidden: Joi.boolean().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }) .xor('form', 'scope') .xor('phases', 'planConfig') .nand('priceConfig', 'scope') .required(), - }, }; module.exports = [ validate(schema), permissions('projectTemplate.create'), - fieldLookupValidation(models.ProjectType, 'key', 'body.param.category', 'Category'), + fieldLookupValidation(models.ProjectType, 'key', 'body.category', 'Category'), (req, res, next) => { - const param = req.body.param; + const param = req.body; const { form, priceConfig, planConfig } = param; return Promise.all([ @@ -66,16 +65,22 @@ module.exports = [ util.checkModel(planConfig, 'PlanConfig', models.PlanConfig, 'project template'), ]) .then(() => { - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { createdBy: req.authUser.userId, updatedBy: req.authUser.userId, }); return models.ProjectTemplate.create(entity) .then((createdEntity) => { + // emit event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PROJECT_TEMPLATE, + createdEntity.toJSON(), + ); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }); }).catch(next); }, diff --git a/src/routes/projectTemplates/create.spec.js b/src/routes/projectTemplates/create.spec.js index 2355548a..54b8eca9 100644 --- a/src/routes/projectTemplates/create.spec.js +++ b/src/routes/projectTemplates/create.spec.js @@ -60,68 +60,65 @@ describe('CREATE project template', () => { describe('POST /projects/metadata/projectTemplates', () => { const body = { - param: { - name: 'template 1', - key: 'key 1', - category: 'generic', - icon: 'http://example.com/icon1.ico', - question: 'question 1', - info: 'info 1', - aliases: ['key-1', 'key_1'], - disabled: true, - hidden: true, - scope: { - scope1: { - subScope1A: 1, - subScope1B: 2, - }, - scope2: [1, 2, 3], + name: 'template 1', + key: 'key 1', + category: 'generic', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + disabled: true, + hidden: true, + scope: { + scope1: { + subScope1A: 1, + subScope1B: 2, }, - phases: { - phase1: { - name: 'phase 1', - details: { - anyDetails: 'any details 1', - }, - others: ['others 11', 'others 12'], + scope2: [1, 2, 3], + }, + phases: { + phase1: { + name: 'phase 1', + details: { + anyDetails: 'any details 1', }, - phase2: { - name: 'phase 2', - details: { - anyDetails: 'any details 2', - }, - others: ['others 21', 'others 22'], + others: ['others 11', 'others 12'], + }, + phase2: { + name: 'phase 2', + details: { + anyDetails: 'any details 2', }, + others: ['others 21', 'others 22'], }, }, }; const newModelBody = { - param: { - name: 'template 1', - key: 'key 1', - category: 'generic', - icon: 'http://example.com/icon1.ico', - question: 'question 1', - info: 'info 1', - aliases: ['key-1', 'key_1'], - disabled: true, - hidden: true, - form: { - key: 'test', - version: 1, - }, - priceConfig: { - key: 'test', - }, - planConfig: { - key: 'test', - }, + name: 'template 1', + key: 'key 1', + category: 'generic', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + disabled: true, + hidden: true, + form: { + key: 'test', + version: 1, + }, + priceConfig: { + key: 'test', }, + planConfig: { + key: 'test', + }, + }; const bodyDefinedFormScope = _.cloneDeep(body); - bodyDefinedFormScope.param.form = { + bodyDefinedFormScope.form = { scope1: { subScope1A: 1, subScope1B: 2, @@ -129,18 +126,18 @@ describe('CREATE project template', () => { scope2: [1, 2, 3], }; const bodyMissingFormScope = _.cloneDeep(body); - delete bodyMissingFormScope.param.scope; + delete bodyMissingFormScope.scope; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -150,7 +147,7 @@ describe('CREATE project template', () => { it('should return 403 for copilot', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -160,7 +157,7 @@ describe('CREATE project template', () => { it('should return 403 for connect manager', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -168,53 +165,52 @@ describe('CREATE project template', () => { .expect(403, done); }); - it('should return 422 if validations dont pass', (done) => { + it('should return 400 if validations dont pass', (done) => { const invalidBody = { - param: { - scope: 'a', - phases: 1, - }, + + scope: 'a', + phases: 1, }; request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if project type is missing', (done) => { + it('should return 400 if project type is missing', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.type = null; + invalidBody.type = null; request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if project type does not exist', (done) => { + it('should return 400 if project type does not exist', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.type = 'not_exist'; + invalidBody.type = 'not_exist'; request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -222,15 +218,15 @@ describe('CREATE project template', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(body.param.name); - resJson.key.should.be.eql(body.param.key); - resJson.category.should.be.eql(body.param.category); + resJson.name.should.be.eql(body.name); + resJson.key.should.be.eql(body.key); + resJson.category.should.be.eql(body.category); resJson.disabled.should.be.eql(true); resJson.hidden.should.be.eql(true); - resJson.scope.should.be.eql(body.param.scope); - resJson.phases.should.be.eql(body.param.phases); + resJson.scope.should.be.eql(body.scope); + resJson.phases.should.be.eql(body.phases); resJson.createdBy.should.be.eql(40051333); // admin should.exist(resJson.createdAt); @@ -245,7 +241,7 @@ describe('CREATE project template', () => { it('should return 201 with new model', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -253,18 +249,18 @@ describe('CREATE project template', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(newModelBody.param.name); - resJson.key.should.be.eql(newModelBody.param.key); - resJson.category.should.be.eql(newModelBody.param.category); + resJson.name.should.be.eql(newModelBody.name); + resJson.key.should.be.eql(newModelBody.key); + resJson.category.should.be.eql(newModelBody.category); resJson.disabled.should.be.eql(true); resJson.hidden.should.be.eql(true); should.not.exist(resJson.scope); should.not.exist(resJson.phase); - resJson.form.should.be.eql(newModelBody.param.form); - resJson.planConfig.should.be.eql(newModelBody.param.planConfig); - resJson.priceConfig.should.be.eql(newModelBody.param.priceConfig); + resJson.form.should.be.eql(newModelBody.form); + resJson.planConfig.should.be.eql(newModelBody.planConfig); + resJson.priceConfig.should.be.eql(newModelBody.priceConfig); resJson.createdBy.should.be.eql(40051333); // admin should.exist(resJson.createdAt); @@ -279,7 +275,7 @@ describe('CREATE project template', () => { it('should return 201 for connect admin', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -287,33 +283,33 @@ describe('CREATE project template', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051336); // connect admin resJson.updatedBy.should.be.eql(40051336); // connect admin done(); }); }); - it('should return 422 if both scope and form are defined', (done) => { + it('should return 400 if both scope and form are defined', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyDefinedFormScope) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if both scope and form are missing', (done) => { + it('should return 400 if both scope and form are missing', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates') + .post('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyMissingFormScope) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); }); }); diff --git a/src/routes/projectTemplates/delete.js b/src/routes/projectTemplates/delete.js index b3f353b6..aaf8055d 100644 --- a/src/routes/projectTemplates/delete.js +++ b/src/routes/projectTemplates/delete.js @@ -2,8 +2,11 @@ * API to delete a project template */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; +import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; @@ -17,9 +20,9 @@ const schema = { module.exports = [ validate(schema), permissions('projectTemplate.delete'), - (req, res, next) => + (req, res, next) => { models.sequelize.transaction(() => - models.ProjectTemplate.findById(req.params.templateId) + models.ProjectTemplate.findByPk(req.params.templateId) .then((entity) => { if (!entity) { const apiErr = new Error(`Project template not found for template id ${req.params.templateId}`); @@ -30,8 +33,17 @@ module.exports = [ return entity.update({ deletedBy: req.authUser.userId }); }) .then(entity => entity.destroy())) - .then(() => { + .then((entity) => { + // emit event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.PROJECT_TEMPLATE, + _.pick(entity.toJSON(), 'id'), + ); + res.status(204).end(); }) - .catch(next), + .catch(next); + }, ]; diff --git a/src/routes/projectTemplates/delete.spec.js b/src/routes/projectTemplates/delete.spec.js index 8c3c268f..580f8504 100644 --- a/src/routes/projectTemplates/delete.spec.js +++ b/src/routes/projectTemplates/delete.spec.js @@ -25,7 +25,7 @@ const expectAfterDelete = (id, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/projects/metadata/projectTemplates/${id}`) + .get(`/v5/projects/metadata/projectTemplates/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -37,57 +37,60 @@ const expectAfterDelete = (id, err, next) => { describe('DELETE project template', () => { let templateId; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProjectTemplate.create({ - name: 'template 1', - key: 'key 1', - category: 'category 1', - icon: 'http://example.com/icon1.ico', - question: 'question 1', - info: 'info 1', - aliases: ['key-1', 'key_1'], - scope: { - scope1: { - subScope1A: 1, - subScope1B: 2, - }, - scope2: [1, 2, 3], - }, - phases: { - phase1: { - name: 'phase 1', - details: { - anyDetails: 'any details 1', + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProjectTemplate.create({ + name: 'template 1', + key: 'key 1', + category: 'category 1', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + scope: { + scope1: { + subScope1A: 1, + subScope1B: 2, }, - others: ['others 11', 'others 12'], + scope2: [1, 2, 3], }, - phase2: { - name: 'phase 2', - details: { - anyDetails: 'any details 2', + phases: { + phase1: { + name: 'phase 1', + details: { + anyDetails: 'any details 1', + }, + others: ['others 11', 'others 12'], + }, + phase2: { + name: 'phase 2', + details: { + anyDetails: 'any details 2', + }, + others: ['others 21', 'others 22'], }, - others: ['others 21', 'others 22'], }, - }, - createdBy: 1, - updatedBy: 1, - })).then((template) => { - templateId = template.id; - return Promise.resolve(); - }), - ); - after(testUtil.clearDb); + createdBy: 1, + updatedBy: 1, + }).then((template) => { + templateId = template.id; + done(); + })); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/projectTemplates/{templateId}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTemplates/${templateId}`) + .delete(`/v5/projects/metadata/projectTemplates/${templateId}`) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTemplates/${templateId}`) + .delete(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -96,7 +99,7 @@ describe('DELETE project template', () => { it('should return 403 for copilot', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTemplates/${templateId}`) + .delete(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -105,7 +108,7 @@ describe('DELETE project template', () => { it('should return 403 for connect manager', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTemplates/${templateId}`) + .delete(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -114,7 +117,7 @@ describe('DELETE project template', () => { it('should return 404 for non-existed template', (done) => { request(server) - .delete('/v4/projects/metadata/projectTemplates/1234') + .delete('/v5/projects/metadata/projectTemplates/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -125,7 +128,7 @@ describe('DELETE project template', () => { models.ProjectTemplate.destroy({ where: { id: templateId } }) .then(() => { request(server) - .delete(`/v4/projects/metadata/projectTemplates/${templateId}`) + .delete(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -135,7 +138,7 @@ describe('DELETE project template', () => { it('should return 204, for admin, if template was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTemplates/${templateId}`) + .delete(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -144,7 +147,7 @@ describe('DELETE project template', () => { it('should return 204, for connect admin, if template was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTemplates/${templateId}`) + .delete(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/projectTemplates/get.js b/src/routes/projectTemplates/get.js index e81c0939..543f0269 100644 --- a/src/routes/projectTemplates/get.js +++ b/src/routes/projectTemplates/get.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import models from '../../models'; +import util from '../../util'; const permissions = tcMiddleware.permissions; @@ -18,24 +18,46 @@ const schema = { module.exports = [ validate(schema), permissions('projectTemplate.view'), - (req, res, next) => models.ProjectTemplate.findOne({ - where: { - deletedAt: { $eq: null }, - id: req.params.templateId, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((projectTemplate) => { - // Not found - if (!projectTemplate) { - const apiErr = new Error(`Project template not found for project id ${req.params.templateId}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => { + util.fetchByIdFromES('projectTemplates', { + query: { + nested: { + path: 'projectTemplates', + query: { + match: { 'projectTemplates.id': req.params.templateId }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No projectTemplate found in ES'); + models.ProjectTemplate.findOne({ + where: { + deletedAt: { $eq: null }, + id: req.params.templateId, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((projectTemplate) => { + // Not found + if (!projectTemplate) { + const apiErr = new Error(`Project template not found for project id ${req.params.templateId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, projectTemplate)); - return Promise.resolve(); + res.json(projectTemplate); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('projectTemplates found in ES'); + res.json(data[0].inner_hits.projectTemplates.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } }) - .catch(next), + .catch(next); + }, ]; diff --git a/src/routes/projectTemplates/get.spec.js b/src/routes/projectTemplates/get.spec.js index 61fc4da0..d4641ecb 100644 --- a/src/routes/projectTemplates/get.spec.js +++ b/src/routes/projectTemplates/get.spec.js @@ -48,19 +48,21 @@ describe('GET project template', () => { let templateId; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProjectTemplate.create(template)) - .then((createdTemplate) => { - templateId = createdTemplate.id; - return Promise.resolve(); - }), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProjectTemplate.create(template).then((createdTemplate) => { + templateId = createdTemplate.id; + done(); + })); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/projectTemplates/{templateId}', () => { it('should return 404 for non-existed template', (done) => { request(server) - .get('/v4/projects/metadata/projectTemplates/1234') + .get('/v5/projects/metadata/projectTemplates/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -71,7 +73,7 @@ describe('GET project template', () => { models.ProjectTemplate.destroy({ where: { id: templateId } }) .then(() => { request(server) - .get(`/v4/projects/metadata/projectTemplates/${templateId}`) + .get(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -81,13 +83,13 @@ describe('GET project template', () => { it('should return 200 for admin', (done) => { request(server) - .get(`/v4/projects/metadata/projectTemplates/${templateId}`) + .get(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(templateId); resJson.name.should.be.eql(template.name); resJson.key.should.be.eql(template.key); @@ -107,13 +109,13 @@ describe('GET project template', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get(`/v4/projects/metadata/projectTemplates/${templateId}`) + .get(`/v5/projects/metadata/projectTemplates/${templateId}`) .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get(`/v4/projects/metadata/projectTemplates/${templateId}`) + .get(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -123,7 +125,7 @@ describe('GET project template', () => { it('should return 200 for connect manager', (done) => { request(server) - .get(`/v4/projects/metadata/projectTemplates/${templateId}`) + .get(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -133,7 +135,7 @@ describe('GET project template', () => { it('should return 200 for member', (done) => { request(server) - .get(`/v4/projects/metadata/projectTemplates/${templateId}`) + .get(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -142,7 +144,7 @@ describe('GET project template', () => { it('should return 200 for copilot', (done) => { request(server) - .get(`/v4/projects/metadata/projectTemplates/${templateId}`) + .get(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/projectTemplates/list.js b/src/routes/projectTemplates/list.js index 3e83f2e4..f278c2ef 100644 --- a/src/routes/projectTemplates/list.js +++ b/src/routes/projectTemplates/list.js @@ -2,22 +2,32 @@ * API to list all project templates */ import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import models from '../../models'; +import util from '../../util'; const permissions = tcMiddleware.permissions; module.exports = [ permissions('projectTemplate.view'), - (req, res, next) => models.ProjectTemplate.findAll({ - where: { - deletedAt: { $eq: null }, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((projectTemplates) => { - res.json(util.wrapResponse(req.id, projectTemplates)); - }) - .catch(next), + (req, res, next) => { + util.fetchFromES('projectTemplates') + .then((data) => { + if (data.projectTemplates.length === 0) { + req.log.debug('No projectTemplate found in ES'); + models.ProjectTemplate.findAll({ + where: { + deletedAt: { $eq: null }, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }).then((projectTemplates) => { + res.json(projectTemplates); + }) + .catch(next); + } else { + req.log.debug('projectTemplates found in ES'); + res.json(data.projectTemplates); + } + }); + }, ]; diff --git a/src/routes/projectTemplates/list.spec.js b/src/routes/projectTemplates/list.spec.js index 2da743c5..02a6a4b4 100644 --- a/src/routes/projectTemplates/list.spec.js +++ b/src/routes/projectTemplates/list.spec.js @@ -65,19 +65,22 @@ describe('LIST project templates', () => { let templateId; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProjectTemplate.create(templates[0])) - .then((createdTemplate) => { - templateId = createdTemplate.id; - return models.ProjectTemplate.create(templates[1]); - }).then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProjectTemplate.create(templates[0])) + .then((createdTemplate) => { + templateId = createdTemplate.id; + return models.ProjectTemplate.create(templates[1]).then(() => done()); + }); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/projectTemplates', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/projectTemplates') + .get('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -85,7 +88,7 @@ describe('LIST project templates', () => { .end((err, res) => { const template = templates[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].id.should.be.eql(templateId); resJson[0].name.should.be.eql(template.name); @@ -106,13 +109,13 @@ describe('LIST project templates', () => { it('should return 403 for anonymous user', (done) => { request(server) - .get('/v4/projects/metadata/projectTemplates') + .get('/v5/projects/metadata/projectTemplates') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/projectTemplates') + .get('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -122,7 +125,7 @@ describe('LIST project templates', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/projectTemplates') + .get('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -132,7 +135,7 @@ describe('LIST project templates', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/projectTemplates') + .get('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -141,7 +144,7 @@ describe('LIST project templates', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/projectTemplates') + .get('/v5/projects/metadata/projectTemplates') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/projectTemplates/update.js b/src/routes/projectTemplates/update.js index 00384a1c..ef3bacb7 100644 --- a/src/routes/projectTemplates/update.js +++ b/src/routes/projectTemplates/update.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import fieldLookupValidation from '../../middlewares/fieldLookupValidation'; import util from '../../util'; import models from '../../models'; @@ -15,52 +16,50 @@ const schema = { params: { templateId: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - name: Joi.string().max(255), - key: Joi.string().max(45), - category: Joi.string().max(45), - icon: Joi.string().max(255), - question: Joi.string().max(255), - info: Joi.string().max(255), - aliases: Joi.array(), - scope: Joi.object().empty(null), - phases: Joi.object().empty(null), - form: Joi.object().keys({ - key: Joi.string().required(), - version: Joi.number(), - }).empty(null), - planConfig: Joi.object().keys({ - key: Joi.string().required(), - version: Joi.number(), - }).empty(null), - priceConfig: Joi.object().keys({ - key: Joi.string().required(), - version: Joi.number(), - }).empty(null), - disabled: Joi.boolean().optional(), - hidden: Joi.boolean().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }) + body: Joi.object().keys({ + id: Joi.any().strip(), + name: Joi.string().max(255), + key: Joi.string().max(45), + category: Joi.string().max(45), + icon: Joi.string().max(255), + question: Joi.string().max(255), + info: Joi.string().max(255), + aliases: Joi.array(), + scope: Joi.object().empty(null), + phases: Joi.object().empty(null), + form: Joi.object().keys({ + key: Joi.string().required(), + version: Joi.number(), + }).empty(null), + planConfig: Joi.object().keys({ + key: Joi.string().required(), + version: Joi.number(), + }).empty(null), + priceConfig: Joi.object().keys({ + key: Joi.string().required(), + version: Joi.number(), + }).empty(null), + disabled: Joi.boolean().optional(), + hidden: Joi.boolean().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }) .xor('form', 'scope') .xor('phases', 'planConfig') .nand('priceConfig', 'scope') .required(), - }, }; module.exports = [ validate(schema), permissions('projectTemplate.edit'), - fieldLookupValidation(models.ProjectType, 'key', 'body.param.category', 'Category'), + fieldLookupValidation(models.ProjectType, 'key', 'body.category', 'Category'), (req, res, next) => { - const param = req.body.param; + const param = req.body; const { form, priceConfig, planConfig } = param; return Promise.all([ @@ -69,7 +68,7 @@ module.exports = [ util.checkModel(planConfig, 'PlanConfig', models.PlanConfig, 'project template'), ]) .then(() => { - const entityToUpdate = _.assign(req.body.param, { + const entityToUpdate = _.assign(req.body, { updatedBy: req.authUser.userId, }); @@ -102,7 +101,14 @@ module.exports = [ return projectTemplate.update(entityToUpdate); }) .then((projectTemplate) => { - res.json(util.wrapResponse(req.id, projectTemplate)); + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, + RESOURCES.PROJECT_TEMPLATE, + entityToUpdate, + ); + + res.json(projectTemplate); }); }).catch(next); }, diff --git a/src/routes/projectTemplates/update.spec.js b/src/routes/projectTemplates/update.spec.js index 22854cf4..85757148 100644 --- a/src/routes/projectTemplates/update.spec.js +++ b/src/routes/projectTemplates/update.spec.js @@ -52,7 +52,8 @@ describe('UPDATE project template', () => { let templateId; - beforeEach(() => testUtil.clearDb() + beforeEach((done) => { + testUtil.clearDb() .then(() => models.ProjectType.bulkCreate([ { key: 'generic', @@ -77,11 +78,9 @@ describe('UPDATE project template', () => { updatedBy: 1, }, ])) - .then(() => models.ProjectTemplate.create(template)) - .then((createdTemplate) => { + .then(() => models.ProjectTemplate.create(template).then((createdTemplate) => { templateId = createdTemplate.id; - return Promise.resolve(); - }) + })) .then(() => models.Form.create({ key: 'test', config: { @@ -111,70 +110,69 @@ describe('UPDATE project template', () => { revision: 1, createdBy: 1, updatedBy: 1, - })), - ); - after(testUtil.clearDb); + }).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('PATCH /projects/metadata/projectTemplates/{templateId}', () => { const body = { - param: { - name: 'template 1 - update', - key: 'key 1 - update', - category: 'concrete', - scope: { - scope1: { - subScope1A: 11, - subScope1C: 'new', - }, - scope2: [4], - scope3: 'new', + + name: 'template 1 - update', + key: 'key 1 - update', + category: 'concrete', + scope: { + scope1: { + subScope1A: 11, + subScope1C: 'new', }, - phases: { - phase1: { - name: 'phase 1 - update', - details: { - anyDetails: 'any details 1 - update', - newDetails: 'new', - }, - others: ['others new'], + scope2: [4], + scope3: 'new', + }, + phases: { + phase1: { + name: 'phase 1 - update', + details: { + anyDetails: 'any details 1 - update', + newDetails: 'new', }, - phase3: { - name: 'phase 3', - details: { - anyDetails: 'any details 3', - }, - others: ['others 31', 'others 32'], + others: ['others new'], + }, + phase3: { + name: 'phase 3', + details: { + anyDetails: 'any details 3', }, + others: ['others 31', 'others 32'], }, }, }; const newModelBody = { - param: { - name: 'template 1', - key: 'key 1', - category: 'generic', - icon: 'http://example.com/icon1.ico', - question: 'question 1', - info: 'info 1', - aliases: ['key-1', 'key_1'], - disabled: true, - hidden: true, - form: { - key: 'test', - version: 1, - }, - priceConfig: { - key: 'test', - }, - planConfig: { - key: 'test', - }, + name: 'template 1', + key: 'key 1', + category: 'generic', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + disabled: true, + hidden: true, + form: { + key: 'test', + version: 1, + }, + priceConfig: { + key: 'test', + }, + planConfig: { + key: 'test', }, }; const bodyDefinedFormScope = _.cloneDeep(body); - bodyDefinedFormScope.param.form = { + bodyDefinedFormScope.form = { scope1: { subScope1A: 1, subScope1B: 2, @@ -182,18 +180,18 @@ describe('UPDATE project template', () => { scope2: [1, 2, 3], }; const bodyMissingFormScope = _.cloneDeep(body); - delete bodyMissingFormScope.param.scope; + delete bodyMissingFormScope.scope; it('should return 403 if user is not authenticated', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -203,7 +201,7 @@ describe('UPDATE project template', () => { it('should return 403 for copilot', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, @@ -213,7 +211,7 @@ describe('UPDATE project template', () => { it('should return 403 for connect manager', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, @@ -221,26 +219,26 @@ describe('UPDATE project template', () => { .expect(403, done); }); - it('should return 422 for invalid request', (done) => { + it('should return 400 for invalid request', (done) => { const invalidBody = { - param: { - scope: 'a', - phases: 1, - }, + + scope: 'a', + phases: 1, + }; request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) - .expect(422, done); + .expect(400, done); }); it('should return 404 for non-existed template', (done) => { request(server) - .patch('/v4/projects/metadata/projectTemplates/1234') + .patch('/v5/projects/metadata/projectTemplates/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -252,7 +250,7 @@ describe('UPDATE project template', () => { models.ProjectTemplate.destroy({ where: { id: templateId } }) .then(() => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -263,18 +261,18 @@ describe('UPDATE project template', () => { it('should return 200 for admin', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(templateId); - resJson.name.should.be.eql(body.param.name); - resJson.key.should.be.eql(body.param.key); - resJson.category.should.be.eql(body.param.category); + resJson.name.should.be.eql(body.name); + resJson.key.should.be.eql(body.key); + resJson.category.should.be.eql(body.category); resJson.scope.should.be.eql({ scope1: { subScope1A: 11, @@ -323,21 +321,21 @@ describe('UPDATE project template', () => { it('should return 200 for new model', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(newModelBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(templateId); - resJson.name.should.be.eql(newModelBody.param.name); - resJson.key.should.be.eql(newModelBody.param.key); - resJson.category.should.be.eql(newModelBody.param.category); - resJson.form.should.be.eql(newModelBody.param.form); - resJson.priceConfig.should.be.eql(newModelBody.param.priceConfig); - resJson.planConfig.should.be.eql(newModelBody.param.planConfig); + resJson.name.should.be.eql(newModelBody.name); + resJson.key.should.be.eql(newModelBody.key); + resJson.category.should.be.eql(newModelBody.category); + resJson.form.should.be.eql(newModelBody.form); + resJson.priceConfig.should.be.eql(newModelBody.priceConfig); + resJson.planConfig.should.be.eql(newModelBody.planConfig); resJson.disabled.should.be.eql(true); resJson.hidden.should.be.eql(true); @@ -354,7 +352,7 @@ describe('UPDATE project template', () => { it('should return 200 for connect admin', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -363,24 +361,24 @@ describe('UPDATE project template', () => { .end(done); }); - it('should return 422 if both scope and form are defined', (done) => { + it('should return 400 if both scope and form are defined', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyDefinedFormScope) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if both scope and form are missing', (done) => { + it('should return 400 if both scope and form are missing', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTemplates/${templateId}`) + .patch(`/v5/projects/metadata/projectTemplates/${templateId}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(bodyMissingFormScope) - .expect(422, done); + .expect(400, done); }); }); }); diff --git a/src/routes/projectTemplates/upgrade.js b/src/routes/projectTemplates/upgrade.js index c6706b80..4c064d26 100644 --- a/src/routes/projectTemplates/upgrade.js +++ b/src/routes/projectTemplates/upgrade.js @@ -5,29 +5,27 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; - const schema = { - body: { - param: Joi.object().keys({ - form: Joi.object().keys({ - version: Joi.number().integer().positive().required(), - key: Joi.string().required(), - }).optional(), - priceConfig: Joi.object().keys({ - version: Joi.number().integer().positive().required(), - key: Joi.string().required(), - }).optional(), - planConfig: Joi.object().keys({ - version: Joi.number().integer().positive().required(), - key: Joi.string().required(), - }).optional(), + body: Joi.object().keys({ + form: Joi.object().keys({ + version: Joi.number().integer().positive().required(), + key: Joi.string().required(), }).optional(), - }, + priceConfig: Joi.object().keys({ + version: Joi.number().integer().positive().required(), + key: Joi.string().required(), + }).optional(), + planConfig: Joi.object().keys({ + version: Joi.number().integer().positive().required(), + key: Joi.string().required(), + }).optional(), + }).optional(), }; @@ -42,19 +40,19 @@ module.exports = [ // eslint-disable-next-line consistent-return }).then(async (pt) => { if (pt == null) { - const apiErr = new Error(`project template not found for id ${req.body.param.templateId}`); + const apiErr = new Error(`project template not found for id ${req.body.templateId}`); apiErr.status = 404; throw apiErr; } if ((pt.scope == null) || (pt.phases == null)) { const apiErr = new Error('Current project template\'s scope or phases is null'); - apiErr.status = 422; + apiErr.status = 400; throw apiErr; } // get form field let newForm = {}; - if (req.body.param.form == null) { + if (req.body.form == null) { const scope = { sections: pt.scope ? pt.scope.sections : null, wizard: pt.scope ? pt.scope.wizard : null, @@ -66,12 +64,12 @@ module.exports = [ key: pt.key, }; } else { - newForm = req.body.param.form; + newForm = req.body.form; await util.checkModel(newForm, 'Form', models.Form, 'project template'); } // get price config field let newPriceConfig = {}; - if (req.body.param.priceConfig == null) { + if (req.body.priceConfig == null) { const config = {}; if (pt.scope) { Object.keys(pt.scope).filter(key => (key !== 'wizard') && (key !== 'sections')).forEach((key) => { @@ -84,19 +82,19 @@ module.exports = [ key: pt.key, }; } else { - newPriceConfig = req.body.param.priceConfig; + newPriceConfig = req.body.priceConfig; await util.checkModel(newPriceConfig, 'PriceConfig', models.PriceConfig, 'project template'); } // get plan config field let newPlanConfig = {}; - if (req.body.param.planConfig == null) { + if (req.body.planConfig == null) { const planConfig = await models.PlanConfig.createNewVersion(pt.key, pt.phases, req.authUser.userId); newPlanConfig = { version: planConfig.version, key: pt.key, }; } else { - newPlanConfig = req.body.param.planConfig; + newPlanConfig = req.body.planConfig; await util.checkModel(newPlanConfig, 'PlanConfig', models.PlanConfig, 'project template'); } @@ -111,8 +109,14 @@ module.exports = [ const newPt = await pt.update(updateInfo); - res.status(201).json(util.wrapResponse( - req.id, _.omit(newPt.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + // emit event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, + RESOURCES.PROJECT_TEMPLATE, + updateInfo); + + res.status(201).json(_.omit(newPt.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next)); }, diff --git a/src/routes/projectTemplates/upgrade.spec.js b/src/routes/projectTemplates/upgrade.spec.js index 71a34aca..28341e8b 100644 --- a/src/routes/projectTemplates/upgrade.spec.js +++ b/src/routes/projectTemplates/upgrade.spec.js @@ -50,109 +50,108 @@ describe('Upgrade project template', () => { let templateId; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProjectType.bulkCreate([ - { - key: 'generic', - displayName: 'Generic', - icon: 'http://example.com/icon1.ico', - question: 'question 1', - info: 'info 1', - aliases: ['key-1', 'key_1'], - metadata: {}, - createdBy: 1, - updatedBy: 1, - }, - { - key: 'concrete', - displayName: 'Concrete', - icon: 'http://example.com/icon1.ico', - question: 'question 2', - info: 'info 2', - aliases: ['key-2', 'key_2'], - metadata: {}, - createdBy: 1, - updatedBy: 1, - }, - ])) - .then(() => { - models.Form.bulkCreate([ - { - key: 'dev', - version: 1, - revision: 1, - config: ['key-1', 'key_1'], - createdBy: 1, - updatedBy: 1, - }, - ]); - }) - .then(() => { - models.PriceConfig.bulkCreate([ + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProjectType.bulkCreate([ { - key: 'dev', - version: 1, - revision: 1, - config: ['key-1', 'key_1'], + key: 'generic', + displayName: 'Generic', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + metadata: {}, createdBy: 1, updatedBy: 1, }, - ]); - }) - .then(() => { - models.PlanConfig.bulkCreate([ { - key: 'dev', - version: 1, - revision: 1, - config: ['key-1', 'key_1'], + key: 'concrete', + displayName: 'Concrete', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + metadata: {}, createdBy: 1, updatedBy: 1, }, - ]); - }) - .then(() => models.ProjectTemplate.create(template)) - .then((createdTemplate) => { - templateId = createdTemplate.id; - return Promise.resolve(); - }), - ); - after(testUtil.clearDb); + ])) + .then(() => { + models.Form.bulkCreate([ + { + key: 'dev', + version: 1, + revision: 1, + config: ['key-1', 'key_1'], + createdBy: 1, + updatedBy: 1, + }, + ]); + }) + .then(() => { + models.PriceConfig.bulkCreate([ + { + key: 'dev', + version: 1, + revision: 1, + config: ['key-1', 'key_1'], + createdBy: 1, + updatedBy: 1, + }, + ]); + }) + .then(() => { + models.PlanConfig.bulkCreate([ + { + key: 'dev', + version: 1, + revision: 1, + config: ['key-1', 'key_1'], + createdBy: 1, + updatedBy: 1, + }, + ]); + }) + .then(() => models.ProjectTemplate.create(template)) + .then((createdTemplate) => { + templateId = createdTemplate.id; + done(); + }); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /projects/metadata/projectTemplates/{templateId}/upgrade', () => { const body = { - param: { - form: { - key: 'dev', - version: 1, - }, - priceConfig: { - key: 'dev', - version: 1, - }, - planConfig: { - key: 'dev', - version: 1, - }, + form: { + key: 'dev', + version: 1, + }, + priceConfig: { + key: 'dev', + version: 1, + }, + planConfig: { + key: 'dev', + version: 1, }, }; const emptyBody = { - param: { - }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`) .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -162,7 +161,7 @@ describe('Upgrade project template', () => { it('should return 403 for copilot', (done) => { request(server) - .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, @@ -172,7 +171,7 @@ describe('Upgrade project template', () => { it('should return 403 for connect manager', (done) => { request(server) - .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, @@ -183,7 +182,7 @@ describe('Upgrade project template', () => { it('should return 404 for non-existed template', (done) => { request(server) - .post('/v4/projects/metadata/projectTemplates/123/upgrade') + .post('/v5/projects/metadata/projectTemplates/123/upgrade') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -195,7 +194,7 @@ describe('Upgrade project template', () => { models.ProjectTemplate.destroy({ where: { id: templateId } }) .then(() => { request(server) - .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -206,14 +205,14 @@ describe('Upgrade project template', () => { it('should return 200 for admin', (done) => { request(server) - .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(templateId); should.not.exist(resJson.scope); @@ -244,16 +243,16 @@ describe('Upgrade project template', () => { }); }); - it('should create new version of model if param not given model key and version', (done) => { + it('should create new version of model if body not given model key and version', (done) => { request(server) - .post(`/v4/projects/metadata/projectTemplates/${templateId}/upgrade`) + .post(`/v5/projects/metadata/projectTemplates/${templateId}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(emptyBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.not.exist(resJson.scope); should.not.exist(resJson.phases); diff --git a/src/routes/projectTypes/create.js b/src/routes/projectTypes/create.js index 8e73e1ec..5f2f6fde 100644 --- a/src/routes/projectTypes/create.js +++ b/src/routes/projectTypes/create.js @@ -5,57 +5,61 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; const schema = { - body: { - param: Joi.object().keys({ - key: Joi.string().max(45).required(), - displayName: Joi.string().max(255).required(), - icon: Joi.string().max(255).required(), - question: Joi.string().max(255).required(), - info: Joi.string().max(255).required(), - aliases: Joi.array().required(), - disabled: Joi.boolean().optional(), - hidden: Joi.boolean().optional(), - metadata: Joi.object().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + key: Joi.string().max(45).required(), + displayName: Joi.string().max(255).required(), + icon: Joi.string().max(255).required(), + question: Joi.string().max(255).required(), + info: Joi.string().max(255).required(), + aliases: Joi.array().required(), + disabled: Joi.boolean().optional(), + hidden: Joi.boolean().optional(), + metadata: Joi.object().required(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ validate(schema), permissions('projectType.create'), (req, res, next) => { - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { createdBy: req.authUser.userId, updatedBy: req.authUser.userId, }); // Check if duplicated key - return models.ProjectType.findById(req.body.param.key) + return models.ProjectType.findByPk(req.body.key) .then((existing) => { if (existing) { const apiErr = new Error(`Project type already exists for key ${req.params.key}`); - apiErr.status = 422; + apiErr.status = 400; return Promise.reject(apiErr); } // Create return models.ProjectType.create(entity); }).then((createdEntity) => { + // emit event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_METADATA_CREATE, + RESOURCES.PROJECT_TYPE, + createdEntity.toJSON()); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next); }, diff --git a/src/routes/projectTypes/create.spec.js b/src/routes/projectTypes/create.spec.js index 7823e45b..003c1aef 100644 --- a/src/routes/projectTypes/create.spec.js +++ b/src/routes/projectTypes/create.spec.js @@ -12,48 +12,50 @@ import models from '../../models'; const should = chai.should(); describe('CREATE project type', () => { - beforeEach(() => testUtil.clearDb() - .then(() => models.ProjectType.create({ - key: 'key1', - displayName: 'displayName 1', - icon: 'http://example.com/icon1.ico', - question: 'question 1', - info: 'info 1', - aliases: ['key-1', 'key_1'], - disabled: false, - hidden: false, - metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } }, - createdBy: 1, - updatedBy: 1, - })).then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProjectType.create({ + key: 'key1', + displayName: 'displayName 1', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + disabled: false, + hidden: false, + metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } }, + createdBy: 1, + updatedBy: 1, + }).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /projects/metadata/projectTypes', () => { const body = { - param: { - key: 'app_dev', - displayName: 'Application Development', - icon: 'prod-cat-app-icon', - info: 'Application Development Info', - question: 'What kind of devlopment you need?', - aliases: ['key-1', 'key_1'], - disabled: true, - hidden: true, - metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } }, - }, + key: 'app_dev', + displayName: 'Application Development', + icon: 'prod-cat-app-icon', + info: 'Application Development Info', + question: 'What kind of devlopment you need?', + aliases: ['key-1', 'key_1'], + disabled: true, + hidden: true, + metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } }, + }; it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -63,7 +65,7 @@ describe('CREATE project type', () => { it('should return 403 for copilot', (done) => { request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -73,7 +75,7 @@ describe('CREATE project type', () => { it('should return 403 for manager', (done) => { request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -81,107 +83,107 @@ describe('CREATE project type', () => { .expect(403, done); }); - it('should return 422 for missing key', (done) => { + it('should return 400 for missing key', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.key; + delete invalidBody.key; request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing displayName', (done) => { + it('should return 400 for missing displayName', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.displayName; + delete invalidBody.displayName; request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing icon', (done) => { + it('should return 400 for missing icon', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.icon; + delete invalidBody.icon; request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing question', (done) => { + it('should return 400 for missing question', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.question; + delete invalidBody.question; request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing info', (done) => { + it('should return 400 for missing info', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.info; + delete invalidBody.info; request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for missing metadata', (done) => { + it('should return 400 for missing metadata', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.metadata; + delete invalidBody.metadata; request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 for duplicated key', (done) => { + it('should return 400 for duplicated key', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.key = 'key1'; + invalidBody.key = 'key1'; request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -189,16 +191,16 @@ describe('CREATE project type', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; - resJson.key.should.be.eql(body.param.key); - resJson.displayName.should.be.eql(body.param.displayName); - resJson.icon.should.be.eql(body.param.icon); - resJson.info.should.be.eql(body.param.info); - resJson.question.should.be.eql(body.param.question); - resJson.aliases.should.be.eql(body.param.aliases); - resJson.disabled.should.be.eql(body.param.disabled); - resJson.hidden.should.be.eql(body.param.hidden); - resJson.metadata.should.be.eql(body.param.metadata); + const resJson = res.body; + resJson.key.should.be.eql(body.key); + resJson.displayName.should.be.eql(body.displayName); + resJson.icon.should.be.eql(body.icon); + resJson.info.should.be.eql(body.info); + resJson.question.should.be.eql(body.question); + resJson.aliases.should.be.eql(body.aliases); + resJson.disabled.should.be.eql(body.disabled); + resJson.hidden.should.be.eql(body.hidden); + resJson.metadata.should.be.eql(body.metadata); resJson.createdBy.should.be.eql(40051333); // admin should.exist(resJson.createdAt); @@ -213,7 +215,7 @@ describe('CREATE project type', () => { it('should return 201 for connect admin', (done) => { request(server) - .post('/v4/projects/metadata/projectTypes') + .post('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -221,16 +223,16 @@ describe('CREATE project type', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; - resJson.key.should.be.eql(body.param.key); - resJson.displayName.should.be.eql(body.param.displayName); - resJson.icon.should.be.eql(body.param.icon); - resJson.info.should.be.eql(body.param.info); - resJson.question.should.be.eql(body.param.question); - resJson.aliases.should.be.eql(body.param.aliases); - resJson.disabled.should.be.eql(body.param.disabled); - resJson.hidden.should.be.eql(body.param.hidden); - resJson.metadata.should.be.eql(body.param.metadata); + const resJson = res.body; + resJson.key.should.be.eql(body.key); + resJson.displayName.should.be.eql(body.displayName); + resJson.icon.should.be.eql(body.icon); + resJson.info.should.be.eql(body.info); + resJson.question.should.be.eql(body.question); + resJson.aliases.should.be.eql(body.aliases); + resJson.disabled.should.be.eql(body.disabled); + resJson.hidden.should.be.eql(body.hidden); + resJson.metadata.should.be.eql(body.metadata); resJson.createdBy.should.be.eql(40051336); // connect admin resJson.updatedBy.should.be.eql(40051336); // connect admin done(); diff --git a/src/routes/projectTypes/delete.js b/src/routes/projectTypes/delete.js index 2f5e2f07..35aeb9fd 100644 --- a/src/routes/projectTypes/delete.js +++ b/src/routes/projectTypes/delete.js @@ -2,8 +2,11 @@ * API to delete a project type */ import validate from 'express-validation'; +import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; +import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; @@ -19,7 +22,7 @@ module.exports = [ permissions('projectType.delete'), (req, res, next) => models.sequelize.transaction(() => - models.ProjectType.findById(req.params.key) + models.ProjectType.findByPk(req.params.key) .then((entity) => { if (!entity) { const apiErr = new Error(`Project type not found for key ${req.params.key}`); @@ -30,7 +33,12 @@ module.exports = [ return entity.update({ deletedBy: req.authUser.userId }); }) .then(entity => entity.destroy())) - .then(() => { + .then((entity) => { + // emit event + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_METADATA_DELETE, + RESOURCES.PROJECT_TYPE, + _.pick(entity.toJSON(), 'key')); res.status(204).end(); }) .catch(next), diff --git a/src/routes/projectTypes/delete.spec.js b/src/routes/projectTypes/delete.spec.js index 5b9cee54..749dc139 100644 --- a/src/routes/projectTypes/delete.spec.js +++ b/src/routes/projectTypes/delete.spec.js @@ -24,7 +24,7 @@ const expectAfterDelete = (key, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/projects/metadata/projectTypes/${key}`) + .get(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -36,31 +36,34 @@ const expectAfterDelete = (key, err, next) => { describe('DELETE project type', () => { const key = 'key1'; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProjectType.create({ - key: 'key1', - displayName: 'displayName 1', - icon: 'http://example.com/icon1.ico', - question: 'question 1', - info: 'info 1', - aliases: ['key-1', 'key_1'], - metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } }, - createdBy: 1, - updatedBy: 1, - })).then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProjectType.create({ + key: 'key1', + displayName: 'displayName 1', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + metadata: { 'slack-notification-mappings': { color: '#96d957', label: 'Full App' } }, + createdBy: 1, + updatedBy: 1, + }).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /projects/metadata/projectTypes/{key}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTypes/${key}`) + .delete(`/v5/projects/metadata/projectTypes/${key}`) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTypes/${key}`) + .delete(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -69,7 +72,7 @@ describe('DELETE project type', () => { it('should return 403 for copilot', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTypes/${key}`) + .delete(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -78,7 +81,7 @@ describe('DELETE project type', () => { it('should return 403 for manager', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTypes/${key}`) + .delete(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -87,7 +90,7 @@ describe('DELETE project type', () => { it('should return 404 for non-existed type', (done) => { request(server) - .delete('/v4/projects/metadata/projectTypes/not_existed') + .delete('/v5/projects/metadata/projectTypes/not_existed') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -98,7 +101,7 @@ describe('DELETE project type', () => { models.ProjectType.destroy({ where: { key } }) .then(() => { request(server) - .delete(`/v4/projects/metadata/projectTypes/${key}`) + .delete(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -108,7 +111,7 @@ describe('DELETE project type', () => { it('should return 204, for admin, if type was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTypes/${key}`) + .delete(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -118,7 +121,7 @@ describe('DELETE project type', () => { it('should return 204, for connect admin, if type was successfully removed', (done) => { request(server) - .delete(`/v4/projects/metadata/projectTypes/${key}`) + .delete(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) diff --git a/src/routes/projectTypes/get.js b/src/routes/projectTypes/get.js index f7eb0b95..2979c559 100644 --- a/src/routes/projectTypes/get.js +++ b/src/routes/projectTypes/get.js @@ -4,8 +4,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import models from '../../models'; +import util from '../../util'; const permissions = tcMiddleware.permissions; @@ -18,22 +18,45 @@ const schema = { module.exports = [ validate(schema), permissions('projectType.view'), - (req, res, next) => models.ProjectType.findOne({ - where: { - key: req.params.key, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) - .then((projectType) => { - // Not found - if (!projectType) { - const apiErr = new Error(`Project type not found for key ${req.params.key}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + (req, res, next) => { + util.fetchByIdFromES('projectTypes', { + query: { + nested: { + path: 'projectTypes', + query: { + match: { 'projectTypes.key': req.params.key }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No projectType found in ES'); + models.ProjectType.findOne({ + where: { + key: req.params.key, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((projectType) => { + // Not found + if (!projectType) { + const apiErr = new Error(`Project type not found for key ${req.params.key}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - res.json(util.wrapResponse(req.id, projectType)); - return Promise.resolve(); + res.json(projectType); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('projectTypes found in ES'); + res.json(data[0].inner_hits.projectTypes.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } }) - .catch(next), + .catch(next); + }, + ]; diff --git a/src/routes/projectTypes/get.spec.js b/src/routes/projectTypes/get.spec.js index 621b30d6..a0fb9e0a 100644 --- a/src/routes/projectTypes/get.spec.js +++ b/src/routes/projectTypes/get.spec.js @@ -27,16 +27,18 @@ describe('GET project type', () => { const key = type.key; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProjectType.create(type)) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProjectType.create(type).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/projectTypes/{key}', () => { it('should return 404 for non-existed type', (done) => { request(server) - .get('/v4/projects/metadata/projectTypes/1234') + .get('/v5/projects/metadata/projectTypes/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -47,7 +49,7 @@ describe('GET project type', () => { models.ProjectType.destroy({ where: { key } }) .then(() => { request(server) - .get(`/v4/projects/metadata/projectTypes/${key}`) + .get(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -57,13 +59,13 @@ describe('GET project type', () => { it('should return 200 for admin', (done) => { request(server) - .get(`/v4/projects/metadata/projectTypes/${key}`) + .get(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(type.key); resJson.displayName.should.be.eql(type.displayName); resJson.icon.should.be.eql(type.icon); @@ -86,13 +88,13 @@ describe('GET project type', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get(`/v4/projects/metadata/projectTypes/${key}`) + .get(`/v5/projects/metadata/projectTypes/${key}`) .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get(`/v4/projects/metadata/projectTypes/${key}`) + .get(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -102,7 +104,7 @@ describe('GET project type', () => { it('should return 200 for connect manager', (done) => { request(server) - .get(`/v4/projects/metadata/projectTypes/${key}`) + .get(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -112,7 +114,7 @@ describe('GET project type', () => { it('should return 200 for member', (done) => { request(server) - .get(`/v4/projects/metadata/projectTypes/${key}`) + .get(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -121,7 +123,7 @@ describe('GET project type', () => { it('should return 200 for copilot', (done) => { request(server) - .get(`/v4/projects/metadata/projectTypes/${key}`) + .get(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/projectTypes/list.js b/src/routes/projectTypes/list.js index 56bc2059..f71f11bd 100644 --- a/src/routes/projectTypes/list.js +++ b/src/routes/projectTypes/list.js @@ -2,19 +2,31 @@ * API to list all project types */ import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import models from '../../models'; +import util from '../../util'; const permissions = tcMiddleware.permissions; module.exports = [ permissions('projectType.view'), - (req, res, next) => models.ProjectType.findAll({ - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((projectTypes) => { - res.json(util.wrapResponse(req.id, projectTypes)); - }) - .catch(next), + (req, res, next) => { + util.fetchFromES('projectTypes') + .then((data) => { + if (data.projectTypes.length === 0) { + req.log.debug('No projectType found in ES'); + models.ProjectType.findAll({ + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((projectTypes) => { + res.json(projectTypes); + }) + .catch(next); + } else { + req.log.debug('projectTypes found in ES'); + res.json(data.projectTypes); + } + }); + }, + ]; diff --git a/src/routes/projectTypes/list.spec.js b/src/routes/projectTypes/list.spec.js index fa2a1c35..05700bd5 100644 --- a/src/routes/projectTypes/list.spec.js +++ b/src/routes/projectTypes/list.spec.js @@ -40,17 +40,19 @@ describe('LIST project types', () => { }, ]; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProjectType.create(types[0])) - .then(() => models.ProjectType.create(types[1])) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProjectType.create(types[0])) + .then(() => models.ProjectType.create(types[1]).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /projects/metadata/projectTypes', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v4/projects/metadata/projectTypes') + .get('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -58,7 +60,7 @@ describe('LIST project types', () => { .end((err, res) => { const type = types[0]; - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].key.should.be.eql(type.key); resJson[0].displayName.should.be.eql(type.displayName); @@ -82,13 +84,13 @@ describe('LIST project types', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/metadata/projectTypes') + .get('/v5/projects/metadata/projectTypes') .expect(403, done); }); it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/projects/metadata/projectTypes') + .get('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -98,7 +100,7 @@ describe('LIST project types', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/projects/metadata/projectTypes') + .get('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -108,7 +110,7 @@ describe('LIST project types', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/projects/metadata/projectTypes') + .get('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -117,7 +119,7 @@ describe('LIST project types', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/projects/metadata/projectTypes') + .get('/v5/projects/metadata/projectTypes') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/projectTypes/update.js b/src/routes/projectTypes/update.js index 9975f478..3bb42b72 100644 --- a/src/routes/projectTypes/update.js +++ b/src/routes/projectTypes/update.js @@ -5,6 +5,7 @@ import validate from 'express-validation'; import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { EVENT, RESOURCES } from '../../constants'; import util from '../../util'; import models from '../../models'; @@ -14,32 +15,30 @@ const schema = { params: { key: Joi.string().max(45).required(), }, - body: { - param: Joi.object().keys({ - key: Joi.any().strip(), - displayName: Joi.string().max(255).optional(), - icon: Joi.string().max(255).optional(), - question: Joi.string().max(255).optional(), - info: Joi.string().max(255).optional(), - aliases: Joi.array().optional(), - disabled: Joi.boolean().optional(), - hidden: Joi.boolean().optional(), - metadata: Joi.object().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + key: Joi.any().strip(), + displayName: Joi.string().max(255).optional(), + icon: Joi.string().max(255).optional(), + question: Joi.string().max(255).optional(), + info: Joi.string().max(255).optional(), + aliases: Joi.array().optional(), + disabled: Joi.boolean().optional(), + hidden: Joi.boolean().optional(), + metadata: Joi.object().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ validate(schema), permissions('projectType.edit'), (req, res, next) => { - const entityToUpdate = _.assign(req.body.param, { + const entityToUpdate = _.assign(req.body, { updatedBy: req.authUser.userId, }); @@ -60,7 +59,13 @@ module.exports = [ return projectType.update(entityToUpdate); }) .then((projectType) => { - res.json(util.wrapResponse(req.id, projectType)); + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, + RESOURCES.PROJECT_TYPE, + entityToUpdate); + + res.json(projectType); return Promise.resolve(); }) .catch(next); diff --git a/src/routes/projectTypes/update.spec.js b/src/routes/projectTypes/update.spec.js index 229f544a..2738e7c2 100644 --- a/src/routes/projectTypes/update.spec.js +++ b/src/routes/projectTypes/update.spec.js @@ -27,36 +27,36 @@ describe('UPDATE project type', () => { }; const key = type.key; - beforeEach(() => testUtil.clearDb() - .then(() => models.ProjectType.create(type)) - .then(() => Promise.resolve()), - ); - after(testUtil.clearDb); + beforeEach((done) => { + testUtil.clearDb() + .then(() => models.ProjectType.create(type).then(() => done())); + }); + after((done) => { + testUtil.clearDb(done); + }); describe('PATCH /projects/metadata/projectTypes/{key}', () => { const body = { - param: { - displayName: 'displayName 1 - update', - icon: 'http://example.com/icon1.ico - update', - question: 'question 1 - update', - info: 'info 1 - update', - aliases: ['key-1-updated', 'key_1_updated'], - disabled: true, - hidden: true, - metadata: { 'slack-notification-mappings': { color: '#b47dd6', label: 'Full App 2' } }, - }, + displayName: 'displayName 1 - update', + icon: 'http://example.com/icon1.ico - update', + question: 'question 1 - update', + info: 'info 1 - update', + aliases: ['key-1-updated', 'key_1_updated'], + disabled: true, + hidden: true, + metadata: { 'slack-notification-mappings': { color: '#b47dd6', label: 'Full App 2' } }, }; it('should return 403 if user is not authenticated', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .send(body) .expect(403, done); }); it('should return 403 for member', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -66,7 +66,7 @@ describe('UPDATE project type', () => { it('should return 403 for copilot', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, @@ -76,7 +76,7 @@ describe('UPDATE project type', () => { it('should return 403 for manager', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, @@ -86,7 +86,7 @@ describe('UPDATE project type', () => { it('should return 404 for non-existed type', (done) => { request(server) - .patch('/v4/projects/metadata/projectTypes/1234') + .patch('/v5/projects/metadata/projectTypes/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -98,7 +98,7 @@ describe('UPDATE project type', () => { models.ProjectType.destroy({ where: { key } }) .then(() => { request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -109,24 +109,24 @@ describe('UPDATE project type', () => { it('should return 200 for admin displayName updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.question; - delete partialBody.param.aliases; - delete partialBody.param.disabled; - delete partialBody.param.hidden; - delete partialBody.param.metadata; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.question; + delete partialBody.aliases; + delete partialBody.disabled; + delete partialBody.hidden; + delete partialBody.metadata; request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); - resJson.displayName.should.be.eql(partialBody.param.displayName); + resJson.displayName.should.be.eql(partialBody.displayName); resJson.icon.should.be.eql(type.icon); resJson.info.should.be.eql(type.info); resJson.question.should.be.eql(type.question); @@ -147,25 +147,25 @@ describe('UPDATE project type', () => { it('should return 200 for admin icon updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.info; - delete partialBody.param.displayName; - delete partialBody.param.question; - delete partialBody.param.aliases; - delete partialBody.param.disabled; - delete partialBody.param.hidden; - delete partialBody.param.metadata; + delete partialBody.info; + delete partialBody.displayName; + delete partialBody.question; + delete partialBody.aliases; + delete partialBody.disabled; + delete partialBody.hidden; + delete partialBody.metadata; request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(type.displayName); - resJson.icon.should.be.eql(partialBody.param.icon); + resJson.icon.should.be.eql(partialBody.icon); resJson.info.should.be.eql(type.info); resJson.question.should.be.eql(type.question); resJson.aliases.should.be.eql(type.aliases); @@ -185,26 +185,26 @@ describe('UPDATE project type', () => { it('should return 200 for admin info updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.displayName; - delete partialBody.param.question; - delete partialBody.param.aliases; - delete partialBody.param.disabled; - delete partialBody.param.hidden; - delete partialBody.param.metadata; + delete partialBody.icon; + delete partialBody.displayName; + delete partialBody.question; + delete partialBody.aliases; + delete partialBody.disabled; + delete partialBody.hidden; + delete partialBody.metadata; request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(type.displayName); resJson.icon.should.be.eql(type.icon); - resJson.info.should.be.eql(partialBody.param.info); + resJson.info.should.be.eql(partialBody.info); resJson.question.should.be.eql(type.question); resJson.aliases.should.be.eql(type.aliases); resJson.disabled.should.be.eql(type.disabled); @@ -223,27 +223,27 @@ describe('UPDATE project type', () => { it('should return 200 for admin question updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.displayName; - delete partialBody.param.aliases; - delete partialBody.param.disabled; - delete partialBody.param.hidden; - delete partialBody.param.metadata; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.displayName; + delete partialBody.aliases; + delete partialBody.disabled; + delete partialBody.hidden; + delete partialBody.metadata; request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(type.displayName); resJson.icon.should.be.eql(type.icon); resJson.info.should.be.eql(type.info); - resJson.question.should.be.eql(partialBody.param.question); + resJson.question.should.be.eql(partialBody.question); resJson.aliases.should.be.eql(type.aliases); resJson.disabled.should.be.eql(type.disabled); resJson.hidden.should.be.eql(type.hidden); @@ -261,28 +261,28 @@ describe('UPDATE project type', () => { it('should return 200 for admin aliases updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.question; - delete partialBody.param.displayName; - delete partialBody.param.disabled; - delete partialBody.param.hidden; - delete partialBody.param.metadata; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.question; + delete partialBody.displayName; + delete partialBody.disabled; + delete partialBody.hidden; + delete partialBody.metadata; request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(type.displayName); resJson.icon.should.be.eql(type.icon); resJson.info.should.be.eql(type.info); resJson.question.should.be.eql(type.question); - resJson.aliases.should.be.eql(partialBody.param.aliases); + resJson.aliases.should.be.eql(partialBody.aliases); resJson.disabled.should.be.eql(type.disabled); resJson.hidden.should.be.eql(type.hidden); resJson.metadata.should.be.eql(type.metadata); @@ -299,29 +299,29 @@ describe('UPDATE project type', () => { it('should return 200 for admin disabled updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.question; - delete partialBody.param.displayName; - delete partialBody.param.aliases; - delete partialBody.param.hidden; - delete partialBody.param.metadata; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.question; + delete partialBody.displayName; + delete partialBody.aliases; + delete partialBody.hidden; + delete partialBody.metadata; request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(type.displayName); resJson.icon.should.be.eql(type.icon); resJson.info.should.be.eql(type.info); resJson.question.should.be.eql(type.question); resJson.aliases.should.be.eql(type.aliases); - resJson.disabled.should.be.eql(partialBody.param.disabled); + resJson.disabled.should.be.eql(partialBody.disabled); resJson.hidden.should.be.eql(type.hidden); resJson.metadata.should.be.eql(type.metadata); resJson.createdBy.should.be.eql(type.createdBy); @@ -337,22 +337,22 @@ describe('UPDATE project type', () => { it('should return 200 for admin hidden updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.question; - delete partialBody.param.displayName; - delete partialBody.param.disabled; - delete partialBody.param.aliases; - delete partialBody.param.metadata; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.question; + delete partialBody.displayName; + delete partialBody.disabled; + delete partialBody.aliases; + delete partialBody.metadata; request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(type.displayName); resJson.icon.should.be.eql(type.icon); @@ -360,7 +360,7 @@ describe('UPDATE project type', () => { resJson.question.should.be.eql(type.question); resJson.aliases.should.be.eql(type.aliases); resJson.disabled.should.be.eql(type.disabled); - resJson.hidden.should.be.eql(partialBody.param.hidden); + resJson.hidden.should.be.eql(partialBody.hidden); resJson.metadata.should.be.eql(type.metadata); resJson.createdBy.should.be.eql(type.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051333); // admin @@ -374,22 +374,22 @@ describe('UPDATE project type', () => { it('should return 200 for admin metadata updated', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.icon; - delete partialBody.param.info; - delete partialBody.param.question; - delete partialBody.param.displayName; - delete partialBody.param.disabled; - delete partialBody.param.aliases; - delete partialBody.param.hidden; + delete partialBody.icon; + delete partialBody.info; + delete partialBody.question; + delete partialBody.displayName; + delete partialBody.disabled; + delete partialBody.aliases; + delete partialBody.hidden; request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(partialBody) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); resJson.displayName.should.be.eql(type.displayName); resJson.icon.should.be.eql(type.icon); @@ -398,7 +398,7 @@ describe('UPDATE project type', () => { resJson.aliases.should.be.eql(type.aliases); resJson.disabled.should.be.eql(type.disabled); resJson.hidden.should.be.eql(type.hidden); - resJson.metadata.should.be.eql(partialBody.param.metadata); + resJson.metadata.should.be.eql(partialBody.metadata); resJson.createdBy.should.be.eql(type.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051333); // admin should.exist(resJson.updatedAt); @@ -411,23 +411,23 @@ describe('UPDATE project type', () => { it('should return 200 for admin all fields updated', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); - resJson.displayName.should.be.eql(body.param.displayName); - resJson.icon.should.be.eql(body.param.icon); - resJson.info.should.be.eql(body.param.info); - resJson.question.should.be.eql(body.param.question); - resJson.aliases.should.be.eql(body.param.aliases); - resJson.disabled.should.be.eql(body.param.disabled); - resJson.hidden.should.be.eql(body.param.hidden); - resJson.metadata.should.be.eql(body.param.metadata); + resJson.displayName.should.be.eql(body.displayName); + resJson.icon.should.be.eql(body.icon); + resJson.info.should.be.eql(body.info); + resJson.question.should.be.eql(body.question); + resJson.aliases.should.be.eql(body.aliases); + resJson.disabled.should.be.eql(body.disabled); + resJson.hidden.should.be.eql(body.hidden); + resJson.metadata.should.be.eql(body.metadata); resJson.createdBy.should.be.eql(type.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051333); // admin should.exist(resJson.updatedAt); @@ -440,23 +440,23 @@ describe('UPDATE project type', () => { it('should return 200 for connect admin', (done) => { request(server) - .patch(`/v4/projects/metadata/projectTypes/${key}`) + .patch(`/v5/projects/metadata/projectTypes/${key}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.key.should.be.eql(key); - resJson.displayName.should.be.eql(body.param.displayName); - resJson.icon.should.be.eql(body.param.icon); - resJson.info.should.be.eql(body.param.info); - resJson.question.should.be.eql(body.param.question); - resJson.aliases.should.be.eql(body.param.aliases); - resJson.disabled.should.be.eql(body.param.disabled); - resJson.hidden.should.be.eql(body.param.hidden); - resJson.metadata.should.be.eql(body.param.metadata); + resJson.displayName.should.be.eql(body.displayName); + resJson.icon.should.be.eql(body.icon); + resJson.info.should.be.eql(body.info); + resJson.question.should.be.eql(body.question); + resJson.aliases.should.be.eql(body.aliases); + resJson.disabled.should.be.eql(body.disabled); + resJson.hidden.should.be.eql(body.hidden); + resJson.metadata.should.be.eql(body.metadata); resJson.createdBy.should.be.eql(type.createdBy); // should not update createdAt resJson.updatedBy.should.be.eql(40051336); // connect admin done(); diff --git a/src/routes/projectUpgrade/create.js b/src/routes/projectUpgrade/create.js index 729f9117..a1fe2fc2 100644 --- a/src/routes/projectUpgrade/create.js +++ b/src/routes/projectUpgrade/create.js @@ -13,6 +13,7 @@ import util from '../../util'; import { PROJECT_STATUS, EVENT, + RESOURCES, } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -25,7 +26,7 @@ const permissions = tcMiddleware.permissions; * @returns {Promise} the latest completed status' creation date, or undefined if not found */ async function findCompletedProjectEndDate(projectId, transaction) { - const projectHistoryRecord = await models.ProjectHistory.find({ + const projectHistoryRecord = await models.ProjectHistory.findOne({ where: { projectId, status: PROJECT_STATUS.COMPLETED }, order: [['createdAt', 'DESC']], attributes: ['createdAt'], @@ -92,7 +93,7 @@ async function migrateFromV2ToV3(req, project, defaultProductTemplateId, phaseNa await models.sequelize.transaction(async (transaction) => { const products = project.details.products; - const projectTemplate = await models.ProjectTemplate.find({ + const projectTemplate = await models.ProjectTemplate.findOne({ where: { key: products[0] }, attributes: ['id', 'phases'], raw: true, @@ -146,7 +147,7 @@ async function migrateFromV2ToV3(req, project, defaultProductTemplateId, phaseNa } else { query = { productKey: phaseProduct.productKey }; } - const productTemplate = await models.ProductTemplate.find({ + const productTemplate = await models.ProductTemplate.findOne({ where: query, attributes: ['id', 'name', 'productKey', 'template'], raw: true, @@ -204,10 +205,10 @@ async function migrateFromV2ToV3(req, project, defaultProductTemplateId, phaseNa correlationId: req.id, }, ); + req.app.emit(EVENT.ROUTING_KEY.PROJECT_UPDATED, { req, - original: previousValue, - updated: project, + updated: _.assign({ resource: RESOURCES.PROJECT }, project.toJSON()), }); } @@ -218,13 +219,11 @@ const allowedMigrations = { }; const schema = { - body: { - param: Joi.object().keys({ - targetVersion: Joi.string().valid(Object.keys(allowedMigrations)).required(), - defaultProductTemplateId: Joi.number().integer().positive().required(), - phaseName: Joi.string(), - }).required(), - }, + body: Joi.object().keys({ + targetVersion: Joi.string().valid(Object.keys(allowedMigrations)).required(), + defaultProductTemplateId: Joi.number().integer().positive().required(), + phaseName: Joi.string(), + }).required(), options: { status: 400, }, @@ -236,9 +235,9 @@ module.exports = [ async (req, res, next) => { try { const projectId = Number(req.params.projectId); - const targetVersion = req.body.param.targetVersion; + const targetVersion = req.body.targetVersion; const targetVersionMigrationData = allowedMigrations[targetVersion]; - const project = await models.Project.find({ where: { id: projectId } }); + const project = await models.Project.findOne({ where: { id: projectId } }); if (!project) { // returning 404 throw util.buildApiError(`project not found for id ${projectId}`, 404); @@ -250,8 +249,8 @@ module.exports = [ targetVersion}`, 400); } // we have a valid project to be migrated - await handler(req, project, req.body.param.defaultProductTemplateId, req.body.param.phaseName); - res.status(200).json(util.wrapResponse(req.id, { message: 'Project successfully migrated' })); + await handler(req, project, req.body.defaultProductTemplateId, req.body.phaseName); + res.status(200).json({ message: 'Project successfully migrated' }); } catch (err) { next(err); } diff --git a/src/routes/projectUpgrade/create.spec.js b/src/routes/projectUpgrade/create.spec.js index 9ab136e0..9ce56413 100644 --- a/src/routes/projectUpgrade/create.spec.js +++ b/src/routes/projectUpgrade/create.spec.js @@ -130,10 +130,8 @@ describe('Project upgrade', () => { updatedBy: 2, }, specific)))); validBody = { - param: { - targetVersion: 'v3', - defaultProductTemplateId: defaultProductTemplate.id, - }, + targetVersion: 'v3', + defaultProductTemplateId: defaultProductTemplate.id, }; // restoring the stubs in beforeEach instead of afterEach because these methods are already stubbed server.services.pubsub.init.restore(); @@ -151,14 +149,14 @@ describe('Project upgrade', () => { it('should return 403 if user is not authenticated', async () => { await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .send(validBody) .expect(403); }); it('should return 403 for non admin', async () => { await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -170,7 +168,7 @@ describe('Project upgrade', () => { // since the product id is extracted from 'details.products', clearing that should trigger this error await project.update({ details: {} }); await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -182,7 +180,7 @@ describe('Project upgrade', () => { // by changing this we cause no matching product template to be found await matchingProductTemplate.update({ productKey: 'non matching product key' }); await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -194,9 +192,9 @@ describe('Project upgrade', () => { // by changing this the default product template id will be used await projectTemplate.update({ phases: { nonMatchingPhase1: { products: ['non existing product'] } } }); // and we simulate a non existing one - validBody.param.defaultProductTemplateId += 1000; + validBody.defaultProductTemplateId += 1000; await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -208,7 +206,7 @@ describe('Project upgrade', () => { // simulate an already migrated project await project.update({ version: 'v3' }); await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -217,9 +215,9 @@ describe('Project upgrade', () => { }); it('should return 400 if there\'s no migration handler for the sent target version', async () => { - validBody.param.targetVersion = 'v4'; + validBody.targetVersion = 'v4'; await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -231,7 +229,7 @@ describe('Project upgrade', () => { // simulate an already migrated project await project.update({ version: 'v3' }); await request(server) - .post(`/v4/projects/${project.id + 1}/upgrade`) + .post(`/v5/projects/${project.id + 1}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -259,7 +257,7 @@ describe('Project upgrade', () => { }); const commonTest = async (testCompleted, completedOnDate, additionalPhaseName) => { - const migratedProject = await models.Project.find({ id: project.id }); + const migratedProject = await models.Project.findOne({ id: project.id }); expect(migratedProject.version).to.equal('v3'); expect(migratedProject.templateId).to.equal(projectTemplate.id); const newProjectPhases = await models.ProjectPhase.findAll({ @@ -311,7 +309,7 @@ describe('Project upgrade', () => { it('should migrate a non completed project to the expected state', async () => { await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -342,7 +340,7 @@ describe('Project upgrade', () => { createdAt: yesterday, }); await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -352,9 +350,9 @@ describe('Project upgrade', () => { }); it('should migrate a project and assign the phase name passed in the parameters', async () => { - validBody.param.phaseName = 'A custom phase name'; + validBody.phaseName = 'A custom phase name'; await request(server) - .post(`/v4/projects/${project.id}/upgrade`) + .post(`/v5/projects/${project.id}/upgrade`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js index fe93fd91..78ec0787 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, REGEX } from '../../constants'; + EVENT, RESOURCES, REGEX } from '../../constants'; import fieldLookupValidation from '../../middlewares/fieldLookupValidation'; import util from '../../util'; import directProject from '../../services/directProject'; @@ -26,48 +26,46 @@ const traverse = require('traverse'); const permissions = require('tc-core-library-js').middleware.permissions; const createProjectValdiations = { - body: { - param: Joi.object().keys({ - name: Joi.string().required(), - description: Joi.string().allow(null).allow('').optional(), - billingAccountId: Joi.number().positive(), - utm: Joi.object().keys({ - source: Joi.string().allow(null), - medium: Joi.string().allow(null), - campaign: Joi.string().allow(null), - }).allow(null), - bookmarks: Joi.array().items(Joi.object().keys({ - title: Joi.string(), - address: Joi.string().regex(REGEX.URL), - })).optional().allow(null), - estimatedPrice: Joi.number().precision(2).positive().optional() + body: Joi.object().keys({ + name: Joi.string().required(), + description: Joi.string().allow(null).allow('').optional(), + billingAccountId: Joi.number().positive(), + utm: Joi.object().keys({ + source: Joi.string().allow(null), + medium: Joi.string().allow(null), + campaign: Joi.string().allow(null), + }).allow(null), + bookmarks: Joi.array().items(Joi.object().keys({ + title: Joi.string(), + address: Joi.string().regex(REGEX.URL), + })).optional().allow(null), + estimatedPrice: Joi.number().precision(2).positive().optional() .allow(null), - terms: Joi.array().items(Joi.number().positive()).optional(), - external: Joi.object().keys({ - id: Joi.string(), - type: Joi.any().valid('github', 'jira', 'asana', 'other'), - data: Joi.string().max(300), // TODO - restrict length - }).allow(null), - type: Joi.string().max(45).required(), - details: Joi.any(), - challengeEligibility: Joi.array().items(Joi.object().keys({ - role: Joi.string().valid('submitter', 'reviewer', 'copilot'), - users: Joi.array().items(Joi.number().positive()), - groups: Joi.array().items(Joi.number().positive()), - })).allow(null), - templateId: Joi.number().integer().positive(), - version: Joi.string(), - estimation: Joi.array().items(Joi.object().keys({ - conditions: Joi.string().required(), - price: Joi.number().required(), - quantity: Joi.number().optional(), - minTime: Joi.number().integer().required(), - maxTime: Joi.number().integer().required(), - buildingBlockKey: Joi.string().required(), - metadata: Joi.object().optional(), - })).optional(), - }).required(), - }, + terms: Joi.array().items(Joi.number().positive()).optional(), + external: Joi.object().keys({ + id: Joi.string(), + type: Joi.any().valid('github', 'jira', 'asana', 'other'), + data: Joi.string().max(300), // TODO - restrict length + }).allow(null), + type: Joi.string().max(45).required(), + details: Joi.any(), + challengeEligibility: Joi.array().items(Joi.object().keys({ + role: Joi.string().valid('submitter', 'reviewer', 'copilot'), + users: Joi.array().items(Joi.number().positive()), + groups: Joi.array().items(Joi.number().positive()), + })).allow(null), + templateId: Joi.number().integer().positive(), + version: Joi.string(), + estimation: Joi.array().items(Joi.object().keys({ + conditions: Joi.string().required(), + price: Joi.number().required(), + quantity: Joi.number().optional(), + minTime: Joi.number().integer().required(), + maxTime: Joi.number().integer().required(), + buildingBlockKey: Joi.string().required(), + metadata: Joi.object().optional(), + })).optional(), + }).required(), }; /** @@ -168,12 +166,12 @@ function validateAndFetchTemplates(templateId) { // backward compatibility for releasing the service before releasing the front end // we ignore missing template id field and create a project without phase/products if (!templateId) return Promise.resolve({}); - return models.ProjectTemplate.findById(templateId, { raw: true }) + return models.ProjectTemplate.findByPk(templateId, { raw: true }) .then((existingProjectTemplate) => { if (!existingProjectTemplate) { // Not found const apiErr = new Error(`Project template not found for id ${templateId}`); - apiErr.status = 422; + apiErr.status = 400; return Promise.reject(apiErr); } return Promise.resolve(existingProjectTemplate); @@ -186,16 +184,16 @@ function validateAndFetchTemplates(templateId) { const productCount = _.isArray(phase.products) ? phase.products.length : 0; if (productCount > config.maxPhaseProductCount) { const apiErr = new Error(`Number of products per phase cannot exceed ${config.maxPhaseProductCount}`); - apiErr.status = 422; + apiErr.status = 400; throw apiErr; } _.map(phase.products, (product) => { - productPromises.push(models.ProductTemplate.findById(product.id) + productPromises.push(models.ProductTemplate.findByPk(product.id) .then((productTemplate) => { if (!productTemplate) { // Not found const apiErr = new Error(`Product template not found for id ${product.id}`); - apiErr.status = 422; + apiErr.status = 400; return Promise.reject(apiErr); } return Promise.resolve(productTemplate); @@ -214,13 +212,13 @@ module.exports = [ // handles request validations validate(createProjectValdiations), permissions('project.create'), - fieldLookupValidation(models.ProjectType, 'key', 'body.param.type', 'Project type'), + fieldLookupValidation(models.ProjectType, 'key', 'body.type', 'Project type'), /** * POST projects/ * Create a project if the user has access */ (req, res, next) => { - const project = req.body.param; + const project = req.body; // by default connect admin and managers joins projects as manager const userRole = util.hasRoles(req, MANAGER_ROLES) ? PROJECT_MEMBER_ROLE.MANAGER @@ -332,8 +330,10 @@ module.exports = [ ); req.log.debug('Sending event to Kafka bus for project %d', newProject.id); // emit event - req.app.emit(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED, { req, project: newProject }); - res.status(201).json(util.wrapResponse(req.id, newProject, 1, 201)); + req.app.emit(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED, + { req, project: _.assign({ resource: RESOURCES.PROJECT }, newProject), + }); + res.status(201).json(newProject); }) .catch((err) => { req.log.error(err.message); diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index ef057d35..8ee47334 100644 --- a/src/routes/projects/create.spec.js +++ b/src/routes/projects/create.spec.js @@ -169,17 +169,15 @@ describe('Project create', () => { describe('POST /projects', () => { const body = { - param: { - type: 'generic', - description: 'test project', - details: {}, - billingAccountId: 1, - name: 'test project1', - bookmarks: [{ - title: 'title1', - address: 'http://www.address.com', - }], - }, + type: 'generic', + description: 'test project', + details: {}, + billingAccountId: 1, + name: 'test project1', + bookmarks: [{ + title: 'title1', + address: 'http://www.address.com', + }], }; let sandbox; @@ -192,115 +190,112 @@ describe('Project create', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/projects') + .post('/v5/projects') .send(body) .expect(403, done); }); - it('should return 422 if validations dont pass', (done) => { + it('should return 400 if validations dont pass', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.name; + delete invalidBody.name; request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if project type is missing', (done) => { + it('should return 400 if project type is missing', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.type = null; + invalidBody.type = null; request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if project type does not exist', (done) => { + it('should return 400 if project type does not exist', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.type = 'not_exist'; + invalidBody.type = 'not_exist'; request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if templateId does not exist', (done) => { + it('should return 400 if templateId does not exist', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.templateId = 3000; + invalidBody.templateId = 3000; request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if phaseProduct count exceeds max value', (done) => { + it('should return 400 if phaseProduct count exceeds max value', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.templateId = 1; + invalidBody.templateId = 1; request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 with wrong format estimation field', (done) => { + it('should return 400 with wrong format estimation field', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.estimation = [ + invalidBody.estimation = [ { }, ]; request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 if error to create direct project', (done) => { const validBody = _.cloneDeep(body); - validBody.param.templateId = 3; + validBody.templateId = 3; const mockHttpClient = _.merge(testUtil.mockHttpClient, { post: () => Promise.reject(new Error('error message')), }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(validBody) .expect('Content-Type', /json/) .expect(201) - .end((err, res) => { + .end((err) => { if (err) { done(err); } else { - const result = res.body.result; - result.success.should.be.truthy; - result.status.should.equal(201); server.services.pubsub.publish.calledWith('project.draft-created').should.be.true; done(); } @@ -309,7 +304,7 @@ describe('Project create', () => { it('should return 201 if valid user and data', (done) => { const validBody = _.cloneDeep(body); - validBody.param.templateId = 3; + validBody.templateId = 3; const mockHttpClient = _.merge(testUtil.mockHttpClient, { post: () => Promise.resolve({ status: 200, @@ -328,7 +323,7 @@ describe('Project create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -339,13 +334,13 @@ describe('Project create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); - resJson.type.should.be.eql(body.param.type); + resJson.type.should.be.eql(body.type); resJson.version.should.be.eql('v3'); resJson.members.should.have.lengthOf(1); resJson.members[0].role.should.be.eql('customer'); @@ -384,7 +379,7 @@ describe('Project create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -395,13 +390,13 @@ describe('Project create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); - resJson.type.should.be.eql(body.param.type); + resJson.type.should.be.eql(body.type); resJson.version.should.be.eql('v2'); resJson.members.should.have.lengthOf(1); resJson.members[0].role.should.be.eql('customer'); @@ -438,24 +433,24 @@ describe('Project create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send(_.merge({ param: { templateId: 3 } }, body)) + .send(_.merge({ templateId: 3 }, body)) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); - resJson.type.should.be.eql(body.param.type); + resJson.type.should.be.eql(body.type); resJson.members.should.have.lengthOf(1); resJson.members[0].role.should.be.eql('customer'); resJson.members[0].userId.should.be.eql(40051331); @@ -488,7 +483,7 @@ describe('Project create', () => { it('should return 201 if valid user and data (with estimation)', (done) => { const validBody = _.cloneDeep(body); - validBody.param.estimation = [ + validBody.estimation = [ { conditions: '( HAS_DESIGN_DELIVERABLE && HAS_ZEPLIN_APP_ADDON && CA_NEEDED)', price: 6, @@ -552,7 +547,7 @@ describe('Project create', () => { buildingBlockKey: 'HAS_UNIT_TESTING_ADDON_CA', }, ]; - validBody.param.templateId = 3; + validBody.templateId = 3; const mockHttpClient = _.merge(testUtil.mockHttpClient, { post: () => Promise.resolve({ status: 200, @@ -571,7 +566,7 @@ describe('Project create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -582,13 +577,13 @@ describe('Project create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); - resJson.type.should.be.eql(body.param.type); + resJson.type.should.be.eql(body.type); resJson.version.should.be.eql('v3'); resJson.members.should.have.lengthOf(1); resJson.members[0].role.should.be.eql('customer'); @@ -659,11 +654,11 @@ describe('Project create', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .post('/v4/projects') + .post('/v5/projects') .set({ Authorization: 'Bearer userId_1800075', }) - .send(_.merge({ param: { templateId: 3 } }, body)) + .send(_.merge({ templateId: 3 }, body)) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { @@ -671,13 +666,13 @@ describe('Project create', () => { server.log.error(err); done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); - resJson.type.should.be.eql(body.param.type); + resJson.type.should.be.eql(body.type); resJson.members.should.have.lengthOf(1); resJson.members[0].role.should.be.eql('customer'); resJson.members[0].userId.should.be.eql(1800075); diff --git a/src/routes/projects/delete.js b/src/routes/projects/delete.js index 915d91d9..65e0caac 100644 --- a/src/routes/projects/delete.js +++ b/src/routes/projects/delete.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import { EVENT } from '../../constants'; +import { EVENT, RESOURCES } from '../../constants'; import models from '../../models'; /** @@ -17,7 +17,7 @@ module.exports = [ const projectId = _.parseInt(req.params.projectId); models.sequelize.transaction(() => - models.Project.findById(req.params.projectId) + models.Project.findByPk(req.params.projectId) .then((entity) => { if (!entity) { const apiErr = new Error(`Project template not found for template id ${projectId}`); @@ -28,14 +28,16 @@ module.exports = [ return entity.update({ deletedBy: req.authUser.userId }); }) .then(project => project.destroy({ cascade: true }))) - .then(() => { + .then((project) => { req.app.services.pubsub.publish( EVENT.ROUTING_KEY.PROJECT_DELETED, { id: projectId }, { correlationId: req.id }, ); // emit event - req.app.emit(EVENT.ROUTING_KEY.PROJECT_DELETED, { req, id: projectId }); + req.app.emit(EVENT.ROUTING_KEY.PROJECT_DELETED, + { req, project: _.assign({ resource: RESOURCES.PROJECT }, _.pick(project.toJSON(), 'id')), + }); res.status(204).json({}); }) .catch(err => next(err)); diff --git a/src/routes/projects/delete.spec.js b/src/routes/projects/delete.spec.js index 0ade73b7..9a9bc3d6 100644 --- a/src/routes/projects/delete.spec.js +++ b/src/routes/projects/delete.spec.js @@ -25,7 +25,7 @@ const expectAfterDelete = (id, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/projects/${id}`) + .get(`/v5/projects/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -106,7 +106,7 @@ describe('Project delete test', () => { describe('DELETE /projects/{id}/', () => { it('should return 403 if copilot tries to delete the project', (done) => { request(server) - .delete(`/v4/projects/${project1.id}`) + .delete(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -115,7 +115,7 @@ describe('Project delete test', () => { it('should return 204 if project was successfully removed', (done) => { request(server) - .delete(`/v4/projects/${project1.id}`) + .delete(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -127,7 +127,7 @@ describe('Project delete test', () => { it('should return 204, for connect admin, if project was successfully removed', (done) => { request(server) - .delete(`/v4/projects/${project1.id}`) + .delete(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -139,7 +139,7 @@ describe('Project delete test', () => { it('should return 204, for connect admin, if project was successfully removed', (done) => { request(server) - .delete(`/v4/projects/${project1.id}`) + .delete(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js index 8942237d..770f6112 100644 --- a/src/routes/projects/get.js +++ b/src/routes/projects/get.js @@ -37,7 +37,7 @@ module.exports = [ }); let project; return models.Project - .find({ + .findOne({ where: { id: projectId }, attributes: _.get(fields, 'projects', null), raw: true, @@ -68,7 +68,7 @@ module.exports = [ }) .then((invites) => { project.invites = invites; - res.status(200).json(util.wrapResponse(req.id, project)); + res.status(200).json(project); }) .catch(err => next(err)); }, diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js index af1b4e30..c6976054 100644 --- a/src/routes/projects/get.spec.js +++ b/src/routes/projects/get.spec.js @@ -75,13 +75,13 @@ describe('GET Project', () => { describe('GET /projects/{id}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get(`/v4/projects/${project2.id}`) + .get(`/v5/projects/${project2.id}`) .expect(403, done); }); it('should return 404 if requested project doesn\'t exist', (done) => { request(server) - .get('/v4/projects/14343323') + .get('/v5/projects/14343323') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -90,7 +90,7 @@ describe('GET Project', () => { it('should return 404 if user does not have access to the project', (done) => { request(server) - .get(`/v4/projects/${project2.id}`) + .get(`/v5/projects/${project2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -99,7 +99,7 @@ describe('GET Project', () => { it('should return the project when registerd member attempts to access the project', (done) => { request(server) - .get(`/v4/projects/${project1.id}/?fields=id%2Cname%2Cstatus%2Cmembers.role%2Cmembers.id%2Cmembers.userId`) + .get(`/v5/projects/${project1.id}/?fields=id%2Cname%2Cstatus%2Cmembers.role%2Cmembers.id%2Cmembers.userId`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -109,7 +109,7 @@ describe('GET Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); should.not.exist(resJson.deletedAt); should.not.exist(resJson.billingAccountId); @@ -123,7 +123,7 @@ describe('GET Project', () => { it('should return the project for administrator ', (done) => { request(server) - .get(`/v4/projects/${project1.id}`) + .get(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -133,7 +133,7 @@ describe('GET Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); done(); } @@ -142,7 +142,7 @@ describe('GET Project', () => { it('should return the project for connect admin ', (done) => { request(server) - .get(`/v4/projects/${project1.id}`) + .get(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -152,7 +152,7 @@ describe('GET Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); done(); } @@ -188,7 +188,7 @@ describe('GET Project', () => { const stub = sinon.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .get(`/v4/projects/${project1.id}`) + .get(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -199,7 +199,7 @@ describe('GET Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); spy.should.have.been.calledOnce; resJson.attachments.should.have.lengthOf(1); diff --git a/src/routes/projects/list-db.js b/src/routes/projects/list-db.js index 187eb6b8..fe8fe940 100644 --- a/src/routes/projects/list-db.js +++ b/src/routes/projects/list-db.js @@ -53,7 +53,7 @@ const retrieveProjects = (req, criteria, sort, ffields) => { promises.push( models.ProjectMember.findAll({ attributes: _.get(fields, 'ProjectMembers'), - where: { projectId: { in: projectIds } }, + where: { projectId: { $in: projectIds } }, raw: true, }), ); @@ -62,7 +62,7 @@ const retrieveProjects = (req, criteria, sort, ffields) => { promises.push( models.ProjectAttachment.findAll({ attributes: PROJECT_ATTACHMENT_ATTRIBUTES, - where: { projectId: { in: projectIds } }, + where: { projectId: { $in: projectIds } }, raw: true, }), ); @@ -94,7 +94,8 @@ module.exports = [ */ (req, res, next) => { // handle filters - let filters = util.parseQueryFilter(req.query.filter); + let filters = _.omit(req.query, 'sort', 'perPage', 'page', 'fields', 'limit', 'offset'); + let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; @@ -108,7 +109,8 @@ module.exports = [ 'name', 'name asc', 'name desc', 'type', 'type asc', 'type desc', ]; - if (!util.isValidFilter(filters, ['id', 'status', 'type', 'memberOnly', 'keyword']) || + // TODO Add customer and manager filters + if (!util.isValidFilter(filters, ['id', 'status', 'type', 'memberOnly', 'keyword', 'name', 'code']) || (sort && _.indexOf(sortableProps, sort) < 0)) { return util.handleError('Invalid filters or sort', null, req, next); } @@ -128,7 +130,7 @@ module.exports = [ || util.hasRoles(req, MANAGER_ROLES))) { // admins & topcoder managers can see all projects return retrieveProjects(req, criteria, sort, req.query.fields) - .then(result => res.json(util.wrapResponse(req.id, result.rows, result.count))) + .then(result => res.json(result.rows)) .catch(err => next(err)); } @@ -136,7 +138,7 @@ module.exports = [ criteria.filters.userId = req.authUser.userId; criteria.filters.email = req.authUser.email; return retrieveProjects(req, criteria, sort, req.query.fields) - .then(result => res.json(util.wrapResponse(req.id, result.rows, result.count))) + .then(result => res.json(result.rows)) .catch(err => next(err)); }, ]; diff --git a/src/routes/projects/list-db.spec.js b/src/routes/projects/list-db.spec.js index f7022e51..7c66ce00 100644 --- a/src/routes/projects/list-db.spec.js +++ b/src/routes/projects/list-db.spec.js @@ -141,13 +141,13 @@ describe('LIST Project db', () => { describe('GET All /projects/', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/db/') + .get('/v5/projects/db/') .expect(403, done); }); it('should return 200 and no projects if user does not have access', (done) => { request(server) - .get(`/v4/projects/db/?filter=id%3Din%28${project2.id}%29`) + .get(`/v5/projects/db/?id=${project2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -156,7 +156,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - res.body.result.content.should.have.lengthOf(0); + res.body.should.have.lengthOf(0); done(); } }); @@ -164,7 +164,7 @@ describe('LIST Project db', () => { it('should return the project when registerd member attempts to access the project', (done) => { request(server) - .get('/v4/projects/db/?filter=status%3Ddraft') + .get('/v5/projects/db/?status=draft') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -174,8 +174,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; - res.body.result.metadata.totalCount.should.equal(1); + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].id.should.equal(project2.id); @@ -186,7 +185,7 @@ describe('LIST Project db', () => { it('should return the project when project that is in reviewed state in which the copilot is its member or has been invited', (done) => { request(server) - .get('/v4/projects/db/') + .get('/v5/projects/db/') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -196,8 +195,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; - res.body.result.metadata.totalCount.should.equal(2); + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(2); done(); @@ -207,7 +205,7 @@ describe('LIST Project db', () => { it('should return the project for administrator ', (done) => { request(server) - .get('/v4/projects/db/?fields=id%2Cmembers.id') + .get('/v5/projects/db/?fields=id%2Cmembers.id') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -217,7 +215,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -227,7 +225,7 @@ describe('LIST Project db', () => { it('should return all projects that match when filtering by name', (done) => { request(server) - .get('/v4/projects/db/?filter=keyword%3Dtest') + .get('/v5/projects/db/?keyword=test') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -237,7 +235,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -247,7 +245,7 @@ describe('LIST Project db', () => { it('should return the project when filtering by keyword, which matches the name', (done) => { request(server) - .get('/v4/projects/db/?filter=keyword%3D1') + .get('/v5/projects/db/?keyword=1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -257,7 +255,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -268,7 +266,7 @@ describe('LIST Project db', () => { it('should return the project when filtering by keyword, which matches the description', (done) => { request(server) - .get('/v4/projects/db/?filter=keyword%3Dproject') + .get('/v5/projects/db/?keyword=project') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -278,7 +276,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -288,7 +286,7 @@ describe('LIST Project db', () => { it('should return the project when filtering by keyword, which matches the details', (done) => { request(server) - .get('/v4/projects/db/?filter=keyword%3Dcode') + .get('/v5/projects/db/?keyword=code') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -298,7 +296,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -310,7 +308,7 @@ describe('LIST Project db', () => { describe('for connect admin ', () => { it('should return the project ', (done) => { request(server) - .get('/v4/projects/db/?fields=id%2Cmembers.id') + .get('/v5/projects/db/?fields=id%2Cmembers.id') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -320,7 +318,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -330,7 +328,7 @@ describe('LIST Project db', () => { it('should return all projects that match when filtering by name', (done) => { request(server) - .get('/v4/projects/db/?filter=keyword%3Dtest') + .get('/v5/projects/db/?keyword=test') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -340,7 +338,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -350,7 +348,7 @@ describe('LIST Project db', () => { it('should return the project when filtering by keyword, which matches the name', (done) => { request(server) - .get('/v4/projects/db/?filter=keyword%3D1') + .get('/v5/projects/db/?keyword=1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -360,7 +358,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -371,7 +369,7 @@ describe('LIST Project db', () => { it('should return the project when filtering by keyword, which matches the description', (done) => { request(server) - .get('/v4/projects/db/?filter=keyword%3Dproject') + .get('/v5/projects/db/?keyword=project') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -381,7 +379,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -391,7 +389,7 @@ describe('LIST Project db', () => { it('should return the project when filtering by keyword, which matches the details', (done) => { request(server) - .get('/v4/projects/db/?filter=keyword%3Dcode') + .get('/v5/projects/db/?keyword=code') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -401,7 +399,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -412,7 +410,7 @@ describe('LIST Project db', () => { it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt"', (done) => { request(server) - .get('/v4/projects/db/?sort=lastActivityAt') + .get('/v5/projects/db/?sort=lastActivityAt') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -421,7 +419,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); resJson[0].name.should.equal('test1'); @@ -434,7 +432,7 @@ describe('LIST Project db', () => { it('should return list of projects ordered descending by lastActivityAt when sort column is "lastActivityAt desc"', (done) => { request(server) - .get('/v4/projects/db/?sort=lastActivityAt desc') + .get('/v5/projects/db/?sort=lastActivityAt desc') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -443,7 +441,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); resJson[0].name.should.equal('test3'); @@ -456,7 +454,7 @@ describe('LIST Project db', () => { it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt asc"', (done) => { request(server) - .get('/v4/projects/db/?sort=lastActivityAt asc') + .get('/v5/projects/db/?sort=lastActivityAt asc') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -465,7 +463,7 @@ describe('LIST Project db', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); resJson[0].name.should.equal('test1'); diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index 52bf3b25..fa83612f 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -286,10 +286,10 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { let mustQuery = []; let shouldQuery = []; let fullTextQuery; - if (_.has(criteria, 'filters.id.$in')) { + if (_.has(criteria, 'filters.id') && _.isArray(criteria.filters.id)) { boolQuery.push({ ids: { - values: criteria.filters.id.$in, + values: criteria.filters.id, }, }); } else if (_.has(criteria, 'filters.id')) { @@ -431,6 +431,68 @@ const parseElasticSearchCriteria = (criteria, fields, order) => { return searchCriteria; }; +const retrieveProjectsFromDB = (req, criteria, sort, ffields) => { + // order by + const order = sort ? [sort.split(' ')] : [['createdAt', 'asc']]; + let fields = ffields ? ffields.split(',') : []; + // parse the fields string to determine what fields are to be returned + fields = util.parseFields(fields, { + projects: PROJECT_ATTRIBUTES, + project_members: PROJECT_MEMBER_ATTRIBUTES, + }); + // make sure project.id is part of fields + if (_.indexOf(fields.projects, 'id') < 0) fields.projects.push('id'); + const retrieveAttachments = !req.query.fields || req.query.fields.indexOf('attachments') > -1; + const retrieveMembers = !req.query.fields || !!fields.project_members.length; + + return models.Project.searchText({ + filters: criteria.filters, + order, + limit: criteria.limit, + offset: criteria.offset, + attributes: _.get(fields, 'projects', null), + }, req.log) + .then(({ rows, count }) => { + const projectIds = _.map(rows, 'id'); + const promises = []; + // retrieve members + if (projectIds.length && retrieveMembers) { + promises.push( + models.ProjectMember.findAll({ + attributes: _.get(fields, 'ProjectMembers'), + where: { projectId: { $in: projectIds } }, + raw: true, + }), + ); + } + if (projectIds.length && retrieveAttachments) { + promises.push( + models.ProjectAttachment.findAll({ + attributes: PROJECT_ATTACHMENT_ATTRIBUTES, + where: { projectId: { $in: projectIds } }, + raw: true, + }), + ); + } + // return results after promise(s) have resolved + return Promise.all(promises) + .then((values) => { + const allMembers = retrieveMembers ? values.shift() : []; + const allAttachments = retrieveAttachments ? values.shift() : []; + _.forEach(rows, (fp) => { + const p = fp; + // if values length is 1 it could be either attachments or members + if (retrieveMembers) { + p.members = _.filter(allMembers, m => m.projectId === p.id); + } + if (retrieveAttachments) { + p.attachments = _.filter(allAttachments, a => a.projectId === p.id); + } + }); + return { rows, count }; + }); + }); +}; const retrieveProjects = (req, criteria, sort, ffields) => { // order by @@ -467,7 +529,8 @@ module.exports = [ */ (req, res, next) => { // handle filters - let filters = util.parseQueryFilter(req.query.filter); + let filters = _.omit(req.query, 'sort', 'perPage', 'page', 'fields', 'limit', 'offset'); + let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; @@ -502,7 +565,13 @@ module.exports = [ || util.hasRoles(req, MANAGER_ROLES))) { // admins & topcoder managers can see all projects return retrieveProjects(req, criteria, sort, req.query.fields) - .then(result => res.json(util.wrapResponse(req.id, result.rows, result.count))) + .then((result) => { + if (result.rows.length === 0) { + return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) + .then(r => res.json(r.rows)); + } + return res.json(result.rows); + }) .catch(err => next(err)); } @@ -510,7 +579,13 @@ module.exports = [ criteria.filters.email = req.authUser.email; criteria.filters.userId = req.authUser.userId; return retrieveProjects(req, criteria, sort, req.query.fields) - .then(result => res.json(util.wrapResponse(req.id, result.rows, result.count))) + .then((result) => { + if (result.rows.length === 0) { + return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) + .then(r => res.json(r.rows)); + } + return res.json(result.rows); + }) .catch(err => next(err)); }, ]; diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index 3edc9f4c..5c826f13 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -259,13 +259,14 @@ describe('LIST Project', () => { describe('GET All /projects/', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/projects/') + .get('/v5/projects/') .expect(403, done); }); it('should return 200 and no projects if user does not have access', (done) => { request(server) - .get(`/v4/projects/?filter=id%3Din%28${project2.id}%29`) + // .get(`/v5/projects/?id=in%28${project2.id}%29`) + .get(`/v5/projects/?id=${project2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -274,7 +275,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - res.body.result.content.should.have.lengthOf(0); + res.body.should.have.lengthOf(0); done(); } }); @@ -282,7 +283,7 @@ describe('LIST Project', () => { it('should return the project when registerd member attempts to access the project', (done) => { request(server) - .get('/v4/projects/?filter=status%3Ddraft') + .get('/v5/projects/?status=draft') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -292,8 +293,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; - res.body.result.metadata.totalCount.should.equal(1); + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); // since project 2 is indexed with id 2 @@ -305,7 +305,7 @@ describe('LIST Project', () => { it('should return the project when project that is in reviewed state in which the copilot is its member or has been invited', (done) => { request(server) - .get('/v4/projects') + .get('/v5/projects') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -315,8 +315,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; - res.body.result.metadata.totalCount.should.equal(2); + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(2); done(); @@ -326,7 +325,7 @@ describe('LIST Project', () => { it('should return the project for administrator ', (done) => { request(server) - .get('/v4/projects/?fields=id%2Cmembers.id') + .get('/v5/projects/?fields=id,members.id') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -336,7 +335,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -347,7 +346,7 @@ describe('LIST Project', () => { it('should return the project for administrator with field description, billingAccountId and attachments', (done) => { request(server) - .get('/v4/projects/?fields=description%2CbillingAccountId%2Cattachments&sort=id%20asc') + .get('/v5/projects/?fields=description,billingAccountId,attachments&sort=id asc') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -357,7 +356,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); resJson[0].should.have.property('attachments'); @@ -379,7 +378,7 @@ describe('LIST Project', () => { it('should return the project for administrator with field description and billingAccountId', (done) => { request(server) - .get('/v4/projects/?fields=description%2CbillingAccountId&sort=id%20asc') + .get('/v5/projects/?fields=description,billingAccountId&sort=id asc') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -389,10 +388,10 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); - resJson[0].should.not.have.property('attachments'); + resJson[0].should.have.property('attachments'); resJson[0].should.have.property('description'); resJson[0].should.have.property('billingAccountId'); done(); @@ -402,7 +401,7 @@ describe('LIST Project', () => { it('should return the project for administrator with all field', (done) => { request(server) - .get('/v4/projects/?sort=id%20asc') + .get('/v5/projects/?sort=id asc') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -412,7 +411,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); resJson[0].should.have.property('id'); @@ -432,7 +431,7 @@ describe('LIST Project', () => { it('should return all projects that match when filtering by name', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3Dtest') + .get('/v5/projects/?keyword=test') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -442,7 +441,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -452,7 +451,7 @@ describe('LIST Project', () => { it('should return the project when filtering by keyword, which matches the name', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3D1') + .get('/v5/projects/?keyword=1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -462,7 +461,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -473,7 +472,7 @@ describe('LIST Project', () => { it('should return the project when filtering by keyword, which matches the description', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3Dproject') + .get('/v5/projects/?keyword=project') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -483,7 +482,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -493,7 +492,7 @@ describe('LIST Project', () => { it('should return the project when filtering by keyword, which matches the member handle', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3Dtourist') + .get('/v5/projects/?keyword=tourist') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -503,7 +502,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -514,7 +513,7 @@ describe('LIST Project', () => { it('should return project that match when filtering by id (exact)', (done) => { request(server) - .get('/v4/projects/?filter=id%3D1') + .get('/v5/projects/?id=1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -524,7 +523,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].id.should.equal(1); @@ -536,7 +535,7 @@ describe('LIST Project', () => { it('should return project that match when filtering by name', (done) => { request(server) - .get('/v4/projects/?filter=name%3Dtest1') + .get('/v5/projects/?name=test1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -546,7 +545,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -557,7 +556,7 @@ describe('LIST Project', () => { it('should return project that match when filtering by name\'s substring', (done) => { request(server) - .get('/v4/projects/?filter=name%3D*st1') + .get('/v5/projects/?name=*st1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -567,7 +566,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -578,7 +577,7 @@ describe('LIST Project', () => { it('should return all projects that match when filtering by details code', (done) => { request(server) - .get('/v4/projects/?filter=code%3Dcode1') + .get('/v5/projects/?code=code1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -588,7 +587,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -600,7 +599,7 @@ describe('LIST Project', () => { it('should return all projects that match when filtering by details code\'s substring', (done) => { request(server) - .get('/v4/projects/?filter=code%3D*de1') + .get('/v5/projects/?code=*de1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -610,7 +609,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -622,7 +621,7 @@ describe('LIST Project', () => { it('should return all projects that match when filtering by customer', (done) => { request(server) - .get('/v4/projects/?filter=customer%3Dfirst*') + .get('/v5/projects/?customer=first*') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -632,7 +631,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -645,7 +644,7 @@ describe('LIST Project', () => { it('should return all projects that match when filtering by manager', (done) => { request(server) - .get('/v4/projects/?filter=manager%3D*ast') + .get('/v5/projects/?manager=*ast') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -655,7 +654,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test3'); @@ -668,7 +667,7 @@ describe('LIST Project', () => { it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt"', (done) => { request(server) - .get('/v4/projects/?sort=lastActivityAt') + .get('/v5/projects/?sort=lastActivityAt') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -677,7 +676,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); resJson[0].name.should.equal('test1'); @@ -690,7 +689,7 @@ describe('LIST Project', () => { it('should return list of projects ordered descending by lastActivityAt when sort column is "lastActivityAt desc"', (done) => { request(server) - .get('/v4/projects/?sort=lastActivityAt desc') + .get('/v5/projects/?sort=lastActivityAt desc') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -699,7 +698,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); resJson[0].name.should.equal('test3'); @@ -712,7 +711,7 @@ describe('LIST Project', () => { it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt asc"', (done) => { request(server) - .get('/v4/projects/?sort=lastActivityAt asc') + .get('/v5/projects/?sort=lastActivityAt asc') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -721,7 +720,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); resJson[0].name.should.equal('test1'); @@ -735,7 +734,7 @@ describe('LIST Project', () => { describe('GET All /projects/ for Connect Admin, ', () => { it('should return the project ', (done) => { request(server) - .get('/v4/projects/?fields=id%2Cmembers.id') + .get('/v5/projects/?fields=id,members.id') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -745,7 +744,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -755,7 +754,7 @@ describe('LIST Project', () => { it('should return all projects, that match when filtering by name', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3Dtest') + .get('/v5/projects/?keyword=test') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -765,7 +764,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -775,7 +774,7 @@ describe('LIST Project', () => { it('should return the project, when filtering by keyword, which matches the name', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3D1') + .get('/v5/projects/?keyword=1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -785,7 +784,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); @@ -796,7 +795,7 @@ describe('LIST Project', () => { it('should return the project, when filtering by keyword, which matches the description', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3Dproject') + .get('/v5/projects/?keyword=project') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -806,7 +805,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(3); done(); @@ -816,7 +815,7 @@ describe('LIST Project', () => { it('should return the project, when filtering by keyword, which matches the member handle', (done) => { request(server) - .get('/v4/projects/?filter=keyword%3Dtourist') + .get('/v5/projects/?keyword=tourist') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -826,7 +825,7 @@ describe('LIST Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); resJson[0].name.should.equal('test1'); diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index 157d33ae..ebf4bc50 100644 --- a/src/routes/projects/update.js +++ b/src/routes/projects/update.js @@ -9,6 +9,7 @@ import { PROJECT_STATUS, PROJECT_MEMBER_ROLE, EVENT, + RESOURCES, USER_ROLE, REGEX, } from '../../constants'; @@ -17,7 +18,6 @@ import directProject from '../../services/directProject'; const traverse = require('traverse'); - /** * API to handle updating a project. */ @@ -39,47 +39,45 @@ const mergeCustomizer = (objValue, srcValue) => { }; const updateProjectValdiations = { - body: { - param: Joi.object().keys({ - id: Joi.number().valid(Joi.ref('$params.id')), - name: Joi.string(), - description: Joi.string().allow(null).allow('').optional(), - billingAccountId: Joi.number().positive(), - directProjectId: Joi.number().positive().allow(null), - status: Joi.any().valid(_.values(PROJECT_STATUS)), - estimatedPrice: Joi.number().precision(2).positive().allow(null), - actualPrice: Joi.number().precision(2).positive(), - terms: Joi.array().items(Joi.number().positive()), - external: Joi.object().keys({ - id: Joi.string(), - type: Joi.any().valid('github', 'jira', 'asana', 'other'), - data: Joi.string().max(300), // TODO - restrict length - }).allow(null), - bookmarks: Joi.array().items(Joi.object().keys({ - title: Joi.string(), - address: Joi.string().regex(REGEX.URL), - })).optional().allow(null), - type: Joi.string().max(45), - details: Joi.any(), - memers: Joi.any(), - templateId: Joi.any().strip(), // ignore the template id - createdBy: Joi.any(), - createdAt: Joi.any(), - updatedBy: Joi.any(), - updatedAt: Joi.any(), - challengeEligibility: Joi.array().items(Joi.object().keys({ - role: Joi.string().valid('submitter', 'reviewer', 'copilot'), - users: Joi.array().items(Joi.number().positive()), - groups: Joi.array().items(Joi.number().positive()), - })).allow(null), + body: Joi.object().keys({ + id: Joi.number().valid(Joi.ref('$params.id')), + name: Joi.string(), + description: Joi.string().allow(null).allow('').optional(), + billingAccountId: Joi.number().positive(), + directProjectId: Joi.number().positive().allow(null), + status: Joi.any().valid(_.values(PROJECT_STATUS)), + estimatedPrice: Joi.number().precision(2).positive().allow(null), + actualPrice: Joi.number().precision(2).positive(), + terms: Joi.array().items(Joi.number().positive()), + external: Joi.object().keys({ + id: Joi.string(), + type: Joi.any().valid('github', 'jira', 'asana', 'other'), + data: Joi.string().max(300), // TODO - restrict length + }).allow(null), + bookmarks: Joi.array().items(Joi.object().keys({ + title: Joi.string(), + address: Joi.string().regex(REGEX.URL), + })).optional().allow(null), + type: Joi.string().max(45), + details: Joi.any(), + memers: Joi.any(), + templateId: Joi.any().strip(), // ignore the template id + createdBy: Joi.any(), + createdAt: Joi.any(), + updatedBy: Joi.any(), + updatedAt: Joi.any(), + challengeEligibility: Joi.array().items(Joi.object().keys({ + role: Joi.string().valid('submitter', 'reviewer', 'copilot'), + users: Joi.array().items(Joi.number().positive()), + groups: Joi.array().items(Joi.number().positive()), + })).allow(null), // cancel reason is mandatory when project status is cancelled - cancelReason: Joi.when('status', { - is: PROJECT_STATUS.CANCELLED, - then: Joi.string().required(), - otherwise: Joi.string().optional(), - }), + cancelReason: Joi.when('status', { + is: PROJECT_STATUS.CANCELLED, + then: Joi.string().required(), + otherwise: Joi.string().optional(), }), - }, + }), }; // NOTE- decided to disable all additional checks for now. @@ -117,14 +115,14 @@ module.exports = [ * Validate project type to be existed. */ (req, res, next) => { - if (req.body.param.type) { - models.ProjectType.findOne({ where: { key: req.body.param.type } }) + if (req.body.type) { + models.ProjectType.findOne({ where: { key: req.body.type } }) .then((projectType) => { if (projectType) { next(); } else { - const err = new Error(`Project type not found for key ${req.body.param.type}`); - err.status = 422; + const err = new Error(`Project type not found for key ${req.body.type}`); + err.status = 400; next(err); } }); @@ -138,7 +136,7 @@ module.exports = [ */ (req, res, next) => { let project; - let updatedProps = req.body.param; + let updatedProps = req.body; const projectId = _.parseInt(req.params.projectId); // prune any fields that cannot be updated directly updatedProps = _.omit(updatedProps, ['createdBy', 'createdAt', 'updatedBy', 'updatedAt', 'id']); @@ -245,9 +243,9 @@ module.exports = [ ); req.app.emit(EVENT.ROUTING_KEY.PROJECT_UPDATED, { req, - original: previousValue, - updated: project, + updated: _.assign({ resource: RESOURCES.PROJECT }, project), }); + // check context for project members project.members = req.context.currentProjectMembers; // get attachments diff --git a/src/routes/projects/update.spec.js b/src/routes/projects/update.spec.js index f1cb51ef..b79da2d1 100644 --- a/src/routes/projects/update.spec.js +++ b/src/routes/projects/update.spec.js @@ -45,10 +45,8 @@ describe('Project', () => { }); describe('PATCH /projects', () => { const body = { - param: { - name: 'updatedProject name', - type: 'generic', - }, + name: 'updatedProject name', + type: 'generic', }; let sandbox; afterEach(() => { @@ -125,14 +123,14 @@ describe('Project', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .send(body) .expect(403, done); }); it('should return 400 if update completed project', (done) => { request(server) - .patch(`/v4/projects/${project2.id}`) + .patch(`/v5/projects/${project2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -143,10 +141,7 @@ describe('Project', () => { if (err) { done(err); } else { - const result = res.body.result; - result.success.should.be.false; - result.status.should.equal(400); - result.content.message.should.equal('Unable to update project'); + res.body.message.should.equal('Unable to update project'); done(); } }); @@ -154,14 +149,12 @@ describe('Project', () => { it('should return 403 if invalid user will update a project', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - status: 'active', - }, + status: 'active', }) .expect('Content-Type', /json/) .expect(403) @@ -169,10 +162,7 @@ describe('Project', () => { if (err) { done(err); } else { - const result = res.body.result; - result.success.should.be.false; - result.status.should.equal(403); - result.content.message.should.equal('Only assigned topcoder-managers or topcoder admins' + + res.body.message.should.equal('Only assigned topcoder-managers or topcoder admins' + ' should be allowed to launch a project'); done(); } @@ -181,25 +171,19 @@ describe('Project', () => { it('should return 200 if topcoder manager user will update a project', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - status: 'active', - }, + status: 'active', }) .expect('Content-Type', /json/) .expect(200) - .end((err, res) => { + .end((err) => { if (err) { done(err); } else { - const result = res.body.result; - result.success.should.be.true; - result.status.should.equal(200); - result.content.status.should.equal('active'); server.services.pubsub.publish.calledWith('project.updated').should.be.true; done(); } @@ -208,7 +192,7 @@ describe('Project', () => { it('should return 200 if valid user and data', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -232,13 +216,12 @@ describe('Project', () => { it('should return 200 and project history should be updated (status is not set)', (done) => { const mbody = { - param: { - name: 'updatedProject name', - status: PROJECT_STATUS.IN_REVIEW, - }, + + name: 'updatedProject name', + status: PROJECT_STATUS.IN_REVIEW, }; request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -280,13 +263,11 @@ describe('Project', () => { it('should return 200 and project history should not be updated (status is not updated)', (done) => { const mbody = { - param: { - name: 'updatedProject name', - status: PROJECT_STATUS.DRAFT, - }, + name: 'updatedProject name', + status: PROJECT_STATUS.DRAFT, }; request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -317,59 +298,51 @@ describe('Project', () => { }); }); - it('should return 422 as cancel reason is mandatory if project status is cancelled', (done) => { + it('should return 400 as cancel reason is mandatory if project status is cancelled', (done) => { const mbody = { - param: { - name: 'updatedProject name', - status: PROJECT_STATUS.CANCELLED, - }, + name: 'updatedProject name', + status: PROJECT_STATUS.CANCELLED, }; request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send(mbody) .expect('Content-Type', /json/) - .expect(422) + .expect(400) .end((err, res) => { if (err) { done(err); } else { - const result = res.body.result; - result.success.should.be.false; - result.status.should.equal(422); + res.body.message.should.equal('validation error: "cancelReason" is required'); done(); } }); }); - it('should return 422 if project type does not exist', (done) => { + it('should return 400 if project type does not exist', (done) => { const mbody = { - param: { - type: 'not_exist', - }, + type: 'not_exist', }; request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send(mbody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 200 and project history should be updated for cancelled project', (done) => { const mbody = { - param: { - name: 'updatedProject name', - status: PROJECT_STATUS.CANCELLED, - cancelReason: 'price/cost', - }, + name: 'updatedProject name', + status: PROJECT_STATUS.CANCELLED, + cancelReason: 'price/cost', }; request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -416,13 +389,11 @@ describe('Project', () => { }) .then(() => { const mbody = { - param: { - name: 'updatedProject name', - status: PROJECT_STATUS.ACTIVE, - }, + name: 'updatedProject name', + status: PROJECT_STATUS.ACTIVE, }; request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -469,13 +440,12 @@ describe('Project', () => { }) .then(() => { const mbody = { - param: { - name: 'updatedProject name', - status: PROJECT_STATUS.ACTIVE, - }, + name: 'updatedProject name', + status: PROJECT_STATUS.ACTIVE, + }; request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -522,13 +492,12 @@ describe('Project', () => { }) .then(() => { const mbody = { - param: { - name: 'updatedProject name', - status: PROJECT_STATUS.ACTIVE, - }, + name: 'updatedProject name', + status: PROJECT_STATUS.ACTIVE, + }; request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -539,9 +508,11 @@ describe('Project', () => { if (err) { done(err); } else { - const result = res.body.result; - result.success.should.be.false; - result.status.should.equal(403); + res + .body + .message + .should + .equal('Only assigned topcoder-managers or topcoder admins should be allowed to launch a project'); done(); } }); @@ -550,7 +521,7 @@ describe('Project', () => { it('should return 200 and project history should not be updated', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -587,14 +558,13 @@ describe('Project', () => { }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - billingAccountId: 123, - }, + billingAccountId: 123, + }) .expect('Content-Type', /json/) .expect(500) @@ -602,10 +572,7 @@ describe('Project', () => { if (err) { done(err); } else { - const result = res.body.result; - result.success.should.be.false; - result.status.should.equal(500); - result.content.message.should.equal('error message'); + res.body.message.should.equal('error message'); done(); } }); @@ -631,14 +598,13 @@ describe('Project', () => { const postSpy = sinon.spy(mockHttpClient, 'post'); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - billingAccountId: 123, - }, + billingAccountId: 123, + }) .expect('Content-Type', /json/) .expect(200) @@ -660,14 +626,13 @@ describe('Project', () => { it('should return 200 and not sync same billing account id', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - billingAccountId: 1, - }, + billingAccountId: 1, + }) .expect('Content-Type', /json/) .expect(200) @@ -687,14 +652,12 @@ describe('Project', () => { it('should return 200 and not sync same billing account id for project without direct project id', (done) => { request(server) - .patch(`/v4/projects/${project3.id}`) + .patch(`/v5/projects/${project3.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - billingAccountId: 1, - }, + billingAccountId: 1, }) .expect('Content-Type', /json/) .expect(200) @@ -715,17 +678,15 @@ describe('Project', () => { it.skip('should return 200 and update bookmarks', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - bookmarks: [{ - title: 'title1', - address: 'address1', - }], - }, + bookmarks: [{ + title: 'title1', + address: 'address1', + }], }) .expect('Content-Type', /json/) .expect(200) @@ -739,14 +700,13 @@ describe('Project', () => { resJson.bookmarks[0].title.should.be.eql('title1'); resJson.bookmarks[0].address.should.be.eql('address1'); request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - bookmarks: null, - }, + bookmarks: null, + }) .expect('Content-Type', /json/) .expect(200) @@ -776,13 +736,13 @@ describe('Project', () => { }) .then(() => { const mbody = { - param: { - name: 'updatedProject name', - status: PROJECT_STATUS.ACTIVE, - }, + + name: 'updatedProject name', + status: PROJECT_STATUS.ACTIVE, + }; request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -834,14 +794,13 @@ describe('Project', () => { it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project status update', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - status: PROJECT_STATUS.COMPLETED, - }, + status: PROJECT_STATUS.COMPLETED, + }) .expect(200) .end((err) => { @@ -849,15 +808,7 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_COMPLETED); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_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; + createEventSpy.calledOnce.should.be.true; done(); }); } @@ -866,15 +817,13 @@ describe('Project', () => { it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project details update', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - details: { - info: 'something', - }, + details: { + info: 'something', }, }) .expect(200) @@ -883,15 +832,15 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_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; + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ resource: 'project' })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ id: project1.id })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ details: { info: 'something' } })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ updatedBy: testUtil.userIds.admin })).should.be.true; done(); }); } @@ -900,14 +849,13 @@ describe('Project', () => { it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project name update', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - name: 'New project name', - }, + name: 'New project name', + }) .expect(200) .end((err) => { @@ -915,15 +863,15 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({ - projectId: project1.id, - projectName: 'New project name', - projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, - userId: 40051333, - initiatorUserId: 40051333, - })).should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ resource: 'project' })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ id: project1.id })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ name: 'New project name' })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ updatedBy: testUtil.userIds.admin })).should.be.true; done(); }); } @@ -932,14 +880,13 @@ describe('Project', () => { it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project description update', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - description: 'Updated description', - }, + description: 'Updated description', + }) .expect(200) .end((err) => { @@ -947,14 +894,15 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_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; + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ resource: 'project' })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ id: project1.id })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ description: 'Updated description' })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ updatedBy: testUtil.userIds.admin })).should.be.true; done(); }); } @@ -963,17 +911,15 @@ describe('Project', () => { it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project bookmarks update', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - bookmarks: [{ - title: 'title1', - address: 'http://someurl.com', - }], - }, + bookmarks: [{ + title: 'title1', + address: 'http://someurl.com', + }], }) .expect(200) .end((err) => { @@ -981,31 +927,30 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_SPECIFICATION_MODIFIED); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_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; + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ resource: 'project' })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ id: project1.id })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ bookmarks: [{ title: 'title1', address: 'http://someurl.com' }] })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, + sinon.match({ updatedBy: testUtil.userIds.admin })).should.be.true; done(); }); } }); }); - it('should not send BUS_API_EVENT.PROJECT_UPDATED message when project estimatedPrice is updated', (done) => { + it('should send BUS_API_EVENT.PROJECT_UPDATED message when project estimatedPrice is updated', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - estimatedPrice: 123, - }, + estimatedPrice: 123, + }) .expect(200) .end((err) => { @@ -1013,23 +958,22 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.notCalled.should.be.true; + createEventSpy.called.should.be.true; done(); }); } }); }); - it('should not send BUS_API_EVENT.PROJECT_UPDATED message when project actualPrice is updated', (done) => { + it('should send BUS_API_EVENT.PROJECT_UPDATED message when project actualPrice is updated', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - actualPrice: 123, - }, + actualPrice: 123, + }) .expect(200) .end((err) => { @@ -1037,23 +981,22 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.notCalled.should.be.true; + createEventSpy.called.should.be.true; done(); }); } }); }); - it('should not send BUS_API_EVENT.PROJECT_UPDATED message when project terms are updated', (done) => { + it('should send BUS_API_EVENT.PROJECT_UPDATED message when project terms are updated', (done) => { request(server) - .patch(`/v4/projects/${project1.id}`) + .patch(`/v5/projects/${project1.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - terms: [1, 2, 3], - }, + terms: [1, 2, 3], + }) .expect(200) .end((err) => { @@ -1061,7 +1004,7 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.notCalled.should.be.true; + createEventSpy.called.should.be.true; done(); }); } diff --git a/src/routes/timelines/create.spec.js b/src/routes/timelines/create.spec.js index 20c88ed8..1ac29ed3 100644 --- a/src/routes/timelines/create.spec.js +++ b/src/routes/timelines/create.spec.js @@ -185,7 +185,9 @@ describe('CREATE timeline', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('POST /timelines', () => { const body = { @@ -201,14 +203,14 @@ describe('CREATE timeline', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .post('/v4/timelines') + .post('/v5/timelines') .send(body) .expect(403, done); }); it('should return 403 for member who is not in the project', (done) => { request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -225,7 +227,7 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -233,7 +235,7 @@ describe('CREATE timeline', () => { .expect(403, done); }); - it('should return 422 if missing name', (done) => { + it('should return 400 if missing name', (done) => { const invalidBody = { param: _.assign({}, body.param, { name: undefined, @@ -241,16 +243,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing startDate', (done) => { + it('should return 400 if missing startDate', (done) => { const invalidBody = { param: _.assign({}, body.param, { startDate: undefined, @@ -258,16 +260,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if startDate is after endDate', (done) => { + it('should return 400 if startDate is after endDate', (done) => { const invalidBody = { param: _.assign({}, body.param, { startDate: '2018-05-29T00:00:00.000Z', @@ -276,16 +278,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing reference', (done) => { + it('should return 400 if missing reference', (done) => { const invalidBody = { param: _.assign({}, body.param, { reference: undefined, @@ -293,16 +295,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing referenceId', (done) => { + it('should return 400 if missing referenceId', (done) => { const invalidBody = { param: _.assign({}, body.param, { referenceId: undefined, @@ -310,16 +312,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if invalid reference', (done) => { + it('should return 400 if invalid reference', (done) => { const invalidBody = { param: _.assign({}, body.param, { reference: 'invalid', @@ -327,16 +329,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if invalid referenceId', (done) => { + it('should return 400 if invalid referenceId', (done) => { const invalidBody = { param: _.assign({}, body.param, { referenceId: 0, @@ -344,16 +346,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if project does not exist', (done) => { + it('should return 400 if project does not exist', (done) => { const invalidBody = { param: _.assign({}, body.param, { referenceId: 1110, @@ -361,16 +363,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if project was deleted', (done) => { + it('should return 400 if project was deleted', (done) => { const invalidBody = { param: _.assign({}, body.param, { referenceId: 2, @@ -378,16 +380,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if phase does not exist', (done) => { + it('should return 400 if phase does not exist', (done) => { const invalidBody = { param: _.assign({}, body.param, { reference: 'phase', @@ -396,16 +398,16 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if phase was deleted', (done) => { + it('should return 400 if phase was deleted', (done) => { const invalidBody = { param: _.assign({}, body.param, { reference: 'phase', @@ -414,18 +416,18 @@ describe('CREATE timeline', () => { }; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 201 for admin', (done) => { request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -460,7 +462,7 @@ describe('CREATE timeline', () => { const withMilestones = _.cloneDeep(body); withMilestones.param.templateId = 1; request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -540,7 +542,7 @@ describe('CREATE timeline', () => { it('should return 201 for connect manager', (done) => { request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -557,7 +559,7 @@ describe('CREATE timeline', () => { it('should return 201 for connect admin', (done) => { request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -574,7 +576,7 @@ describe('CREATE timeline', () => { it('should return 201 for copilot', (done) => { request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -591,7 +593,7 @@ describe('CREATE timeline', () => { it('should return 201 for member', (done) => { request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -614,7 +616,7 @@ describe('CREATE timeline', () => { }, }); request(server) - .post('/v4/timelines') + .post('/v5/timelines') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/timelines/delete.spec.js b/src/routes/timelines/delete.spec.js index 68c505b2..e9f6d1c3 100644 --- a/src/routes/timelines/delete.spec.js +++ b/src/routes/timelines/delete.spec.js @@ -28,7 +28,7 @@ const expectAfterDelete = (id, err, next) => { chai.assert.isNotNull(res.deletedBy); request(server) - .get(`/v4/timelines/${id}`) + .get(`/v5/timelines/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -208,19 +208,21 @@ describe('DELETE timeline', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('DELETE /timelines/{timelineId}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .delete('/v4/timelines/1') + .delete('/v5/timelines/1') .expect(403, done); }); it('should return 403 for member who is not in the project', (done) => { request(server) - .delete('/v4/timelines/1') + .delete('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -229,7 +231,7 @@ describe('DELETE timeline', () => { it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => { request(server) - .delete('/v4/timelines/2') + .delete('/v5/timelines/2') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -238,7 +240,7 @@ describe('DELETE timeline', () => { it('should return 404 for non-existed timeline', (done) => { request(server) - .delete('/v4/timelines/1234') + .delete('/v5/timelines/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -247,20 +249,20 @@ describe('DELETE timeline', () => { it('should return 404 for deleted timeline', (done) => { request(server) - .delete('/v4/timelines/3') + .delete('/v5/timelines/3') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(404, done); }); - it('should return 422 for invalid param', (done) => { + it('should return 400 for invalid param', (done) => { request(server) - .delete('/v4/timelines/0') + .delete('/v5/timelines/0') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); // eslint-disable-next-line func-names @@ -272,7 +274,7 @@ describe('DELETE timeline', () => { results.should.have.length(2); request(server) - .delete('/v4/timelines/1') + .delete('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -298,7 +300,7 @@ describe('DELETE timeline', () => { it('should return 204, for connect admin, if timeline was successfully removed', (done) => { request(server) - .delete('/v4/timelines/1') + .delete('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -308,7 +310,7 @@ describe('DELETE timeline', () => { it('should return 204, for connect manager, if timeline was successfully removed', (done) => { request(server) - .delete('/v4/timelines/1') + .delete('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -318,7 +320,7 @@ describe('DELETE timeline', () => { it('should return 204, for copilot, if timeline was successfully removed', (done) => { request(server) - .delete('/v4/timelines/1') + .delete('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -328,7 +330,7 @@ describe('DELETE timeline', () => { it('should return 204, for member, if timeline was successfully removed', (done) => { request(server) - .delete('/v4/timelines/1') + .delete('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) diff --git a/src/routes/timelines/get.spec.js b/src/routes/timelines/get.spec.js index 2331295c..67f5726d 100644 --- a/src/routes/timelines/get.spec.js +++ b/src/routes/timelines/get.spec.js @@ -182,18 +182,20 @@ describe('GET timeline', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /timelines/{timelineId}', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/timelines/1') + .get('/v5/timelines/1') .expect(403, done); }); it('should return 403 for member who is not in the project', (done) => { request(server) - .get('/v4/timelines/1') + .get('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -202,7 +204,7 @@ describe('GET timeline', () => { it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => { request(server) - .get('/v4/timelines/2') + .get('/v5/timelines/2') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -211,7 +213,7 @@ describe('GET timeline', () => { it('should return 404 for non-existed timeline', (done) => { request(server) - .get('/v4/timelines/1234') + .get('/v5/timelines/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -220,25 +222,25 @@ describe('GET timeline', () => { it('should return 404 for deleted timeline', (done) => { request(server) - .get('/v4/timelines/3') + .get('/v5/timelines/3') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(404, done); }); - it('should return 422 for invalid param', (done) => { + it('should return 400 for invalid param', (done) => { request(server) - .get('/v4/timelines/0') + .get('/v5/timelines/0') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); it('should return 200 for admin', (done) => { request(server) - .get('/v4/timelines/1') + .get('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -269,7 +271,7 @@ describe('GET timeline', () => { it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/timelines/1') + .get('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -279,7 +281,7 @@ describe('GET timeline', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/timelines/1') + .get('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -289,7 +291,7 @@ describe('GET timeline', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/timelines/1') + .get('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -298,7 +300,7 @@ describe('GET timeline', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/timelines/1') + .get('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/timelines/list.spec.js b/src/routes/timelines/list.spec.js index 2c1395c8..b69e0557 100644 --- a/src/routes/timelines/list.spec.js +++ b/src/routes/timelines/list.spec.js @@ -207,48 +207,50 @@ describe('LIST timelines', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('GET /timelines', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .get('/v4/timelines') + .get('/v5/timelines') .expect(403, done); }); - it('should return 422 for invalid filter key', (done) => { + it('should return 400 for invalid filter key', (done) => { request(server) - .get('/v4/timelines?filter=invalid%3Dproject') + .get('/v5/timelines?filter=invalid%3Dproject') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) - .expect(422) + .expect(400) .end(done); }); - it('should return 422 for invalid reference filter', (done) => { + it('should return 400 for invalid reference filter', (done) => { request(server) - .get('/v4/timelines?filter=reference%3Dinvalid%26referenceId%3D1') + .get('/v5/timelines?filter=reference%3Dinvalid%26referenceId%3D1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) - .expect(422) + .expect(400) .end(done); }); - it('should return 422 for invalid referenceId filter', (done) => { + it('should return 400 for invalid referenceId filter', (done) => { request(server) - .get('/v4/timelines?filter=reference%3Dinvalid%26referenceId%3D0') + .get('/v5/timelines?filter=reference%3Dinvalid%26referenceId%3D0') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) - .expect(422) + .expect(400) .end(done); }); it('should return 200 for admin', (done) => { request(server) - .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -283,7 +285,7 @@ describe('LIST timelines', () => { it('should return 200 for connect admin', (done) => { request(server) - .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -298,7 +300,7 @@ describe('LIST timelines', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -313,7 +315,7 @@ describe('LIST timelines', () => { it('should return 200 for member', (done) => { request(server) - .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -327,7 +329,7 @@ describe('LIST timelines', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -341,7 +343,7 @@ describe('LIST timelines', () => { it('should return 403 for member with not accessible project', (done) => { request(server) - .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -350,7 +352,7 @@ describe('LIST timelines', () => { it('should return 200 with reference and referenceId filter', (done) => { request(server) - .get('/v4/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) diff --git a/src/routes/timelines/update.spec.js b/src/routes/timelines/update.spec.js index 72fffcb3..598c1421 100644 --- a/src/routes/timelines/update.spec.js +++ b/src/routes/timelines/update.spec.js @@ -186,7 +186,9 @@ describe('UPDATE timeline', () => { }); }); - after(testUtil.clearDb); + after((done) => { + testUtil.clearDb(done); + }); describe('PATCH /timelines/{timelineId}', () => { const body = { @@ -202,14 +204,14 @@ describe('UPDATE timeline', () => { it('should return 403 if user is not authenticated', (done) => { request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .send(body) .expect(403, done); }); it('should return 403 for member who is not in the project', (done) => { request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -219,7 +221,7 @@ describe('UPDATE timeline', () => { it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => { request(server) - .patch('/v4/timelines/2') + .patch('/v5/timelines/2') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, @@ -229,7 +231,7 @@ describe('UPDATE timeline', () => { it('should return 404 for non-existed timeline', (done) => { request(server) - .patch('/v4/timelines/1234') + .patch('/v5/timelines/1234') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, @@ -239,7 +241,7 @@ describe('UPDATE timeline', () => { it('should return 404 for deleted timeline', (done) => { request(server) - .patch('/v4/timelines/3') + .patch('/v5/timelines/3') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, @@ -247,19 +249,19 @@ describe('UPDATE timeline', () => { .expect(404, done); }); - it('should return 422 for invalid param', (done) => { + it('should return 400 for invalid param', (done) => { request(server) - .patch('/v4/timelines/0') + .patch('/v5/timelines/0') .send(body) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(422, done); + .expect(400, done); }); it('should return 404 for non-existed template', (done) => { request(server) - .patch('/v4/timelines/1234') + .patch('/v5/timelines/1234') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -269,7 +271,7 @@ describe('UPDATE timeline', () => { it('should return 404 for deleted template', (done) => { request(server) - .patch('/v4/timelines/3') + .patch('/v5/timelines/3') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -277,7 +279,7 @@ describe('UPDATE timeline', () => { .expect(404, done); }); - it('should return 422 if missing name', (done) => { + it('should return 400 if missing name', (done) => { const invalidBody = { param: _.assign({}, body.param, { name: undefined, @@ -285,16 +287,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing startDate', (done) => { + it('should return 400 if missing startDate', (done) => { const invalidBody = { param: _.assign({}, body.param, { startDate: undefined, @@ -302,16 +304,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if startDate is after endDate', (done) => { + it('should return 400 if startDate is after endDate', (done) => { const invalidBody = { param: _.assign({}, body.param, { startDate: '2018-05-29T00:00:00.000Z', @@ -320,16 +322,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing reference', (done) => { + it('should return 400 if missing reference', (done) => { const invalidBody = { param: _.assign({}, body.param, { reference: undefined, @@ -337,16 +339,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if missing referenceId', (done) => { + it('should return 400 if missing referenceId', (done) => { const invalidBody = { param: _.assign({}, body.param, { referenceId: undefined, @@ -354,16 +356,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if invalid reference', (done) => { + it('should return 400 if invalid reference', (done) => { const invalidBody = { param: _.assign({}, body.param, { reference: 'invalid', @@ -371,16 +373,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if invalid referenceId', (done) => { + it('should return 400 if invalid referenceId', (done) => { const invalidBody = { param: _.assign({}, body.param, { referenceId: 0, @@ -388,16 +390,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if project does not exist', (done) => { + it('should return 400 if project does not exist', (done) => { const invalidBody = { param: _.assign({}, body.param, { referenceId: 1110, @@ -405,16 +407,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if project was deleted', (done) => { + it('should return 400 if project was deleted', (done) => { const invalidBody = { param: _.assign({}, body.param, { referenceId: 2, @@ -422,16 +424,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if phase does not exist', (done) => { + it('should return 400 if phase does not exist', (done) => { const invalidBody = { param: _.assign({}, body.param, { reference: 'phase', @@ -440,16 +442,16 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); - it('should return 422 if phase was deleted', (done) => { + it('should return 400 if phase was deleted', (done) => { const invalidBody = { param: _.assign({}, body.param, { reference: 'phase', @@ -458,18 +460,18 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(422, done); + .expect(400, done); }); it('should return 200 for admin', (done) => { request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -504,7 +506,7 @@ describe('UPDATE timeline', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -517,12 +519,12 @@ describe('UPDATE timeline', () => { .expect(200) .end(() => { setTimeout(() => { - models.Milestone.findById(1) + models.Milestone.findByPk(1) .then((milestone) => { milestone.startDate.should.be.eql(new Date('2018-05-15T00:00:00.000Z')); milestone.endDate.should.be.eql(new Date('2018-05-16T00:00:00.000Z')); }) - .then(() => models.Milestone.findById(2)) + .then(() => models.Milestone.findByPk(2)) .then((milestone) => { milestone.startDate.should.be.eql(new Date('2018-05-17T00:00:00.000Z')); milestone.endDate.should.be.eql(new Date('2018-05-19T00:00:00.000Z')); @@ -538,7 +540,7 @@ describe('UPDATE timeline', () => { this.timeout(10000); request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -551,12 +553,12 @@ describe('UPDATE timeline', () => { .expect(200) .end(() => { setTimeout(() => { - models.Milestone.findById(1) + models.Milestone.findByPk(1) .then((milestone) => { milestone.startDate.should.be.eql(new Date('2018-05-12T00:00:00.000Z')); milestone.endDate.should.be.eql(new Date('2018-05-13T00:00:00.000Z')); }) - .then(() => models.Milestone.findById(2)) + .then(() => models.Milestone.findByPk(2)) .then((milestone) => { milestone.startDate.should.be.eql(new Date('2018-05-14T00:00:00.000Z')); milestone.endDate.should.be.eql(new Date('2018-05-16T00:00:00.000Z')); @@ -570,7 +572,7 @@ describe('UPDATE timeline', () => { it('should return 200 for connect admin', (done) => { request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -581,7 +583,7 @@ describe('UPDATE timeline', () => { it('should return 200 for connect manager', (done) => { request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -592,7 +594,7 @@ describe('UPDATE timeline', () => { it('should return 200 for copilot', (done) => { request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) @@ -603,7 +605,7 @@ describe('UPDATE timeline', () => { it('should return 200 for member', (done) => { request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -621,7 +623,7 @@ describe('UPDATE timeline', () => { }; request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -651,7 +653,7 @@ describe('UPDATE timeline', () => { // thus TIMELINE_ADJUSTED will be always sent it('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when timeline updated', (done) => { request(server) - .patch('/v4/timelines/1') + .patch('/v5/timelines/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/util.js b/src/util.js index 7aa7b731..c56de049 100644 --- a/src/util.js +++ b/src/util.js @@ -320,6 +320,39 @@ _.assignIn(util, { return esClient; }, + /** + * Return the searched resource from elastic search + * @return {Object} the searched resource + */ + fetchFromES: Promise.coroutine(function* () { // eslint-disable-line func-names + const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); + const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); + const data = (yield esClient.search({ index: ES_METADATA_INDEX, type: ES_METADATA_TYPE })); + return data.hits.hits.length > 0 ? data.hits.hits[0]._source : { // eslint-disable-line no-underscore-dangle + productTemplates: [], + forms: [], + projectTemplates: [], + planConfigs: [], + priceConfigs: [], + projectTypes: [], + productCategories: [], + orgConfigs: [] }; + }), + + /** + * Return the searched resource from elastic search + * @return {Array} the searched resource + */ + fetchByIdFromES: Promise.coroutine(function* (resource, query) { // eslint-disable-line func-names + const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); + const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); + return (yield esClient.search({ index: ES_METADATA_INDEX, + type: ES_METADATA_TYPE, + _source: false, + body: query, + })).hits.hits; + }), + /** * Retrieve member details from userIds */ @@ -383,6 +416,19 @@ _.assignIn(util, { } }), + /** + * Send resource to kafka bus + * @param {object} req Request object + * @param {String} key the event key + * @param {String} name the resource name + * @param {object} resource the resource + */ + sendResourceToKafkaBus: Promise.coroutine(function* (req, key, name, resource) { // eslint-disable-line + req.log.debug('Sending event to Kafka bus for resource %s %s', name, resource.id || resource.key); + // emit event + req.app.emit(key, { req, resource: _.assign({ resource: name }, resource) }); + }), + /** * Add userId to project * @param {object} req Request object that should contain project info and user info @@ -412,7 +458,7 @@ _.assignIn(util, { newMember, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, { req, member: newMember }); + return newMember; }) .catch((err) => { @@ -495,7 +541,7 @@ _.assignIn(util, { })).then((record) => { if (_.isNil(record)) { const apiErr = new Error(errorMessage); - apiErr.status = 422; + apiErr.status = 400; throw apiErr; } }); @@ -509,7 +555,7 @@ _.assignIn(util, { })).then((record) => { if (_.isNil(record)) { const apiErr = new Error(errorMessage); - apiErr.status = 422; + apiErr.status = 400; throw apiErr; } }); From 0e37b3400a549d1a512c18f17471dcb89bc27509 Mon Sep 17 00:00:00 2001 From: Sharathkumar Anbu Date: Tue, 18 Jun 2019 21:56:42 +0530 Subject: [PATCH 03/88] Credential clean up --- config/default.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/config/default.json b/config/default.json index 7578a9bc..bc2f0dc6 100644 --- a/config/default.json +++ b/config/default.json @@ -46,13 +46,8 @@ "busApiToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicHJvamVjdC1zZXJ2aWNlIiwiaWF0IjoxNTEyNzQ3MDgyLCJleHAiOjE1MjEzODcwODJ9.PHuNcFDaotGAL8RhQXQMdpL8yOKXxjB5DbBIodmt7RE", "HEALTH_CHECK_URL": "_health", "maxPhaseProductCount": 1, - "AUTH0_CLIENT_ID": "maE2maBSv9fRVHjSlC31LFZSq6VhhZqC", - "AUTH0_CLIENT_SECRET": "v3dcgVp4-_Lgk-8UhyekOA7CyyBvFBl1xipl97QqeGy3WyTk3poyvpsDC0sRAKoo", - "AUTH0_AUDIENCE": "https://m2m.topcoder-dev.com/", - "AUTH0_URL": "https://topcoder-dev.auth0.com/oauth/token", "TOKEN_CACHE_TIME": "86000", "whitelistedOriginsForUserIdAuth": "[\"https:\/\/topcoder-newauth.auth0.com\/\",\"https:\/\/api.topcoder-dev.com\"]", - "AUTH0_PROXY_SERVER_URL" : "https://topcoder-dev.auth0.com/oauth/token", "EMAIL_INVITE_FROM_NAME":"Topcoder", "EMAIL_INVITE_FROM_EMAIL":"noreply@connect.topcoder.com", "inviteEmailSubject": "You are invited to Topcoder", From d9903401255aaa8002106e06b6cfc1baa6bae47c Mon Sep 17 00:00:00 2001 From: Sharathkumar Anbu Date: Mon, 24 Jun 2019 16:45:26 +0530 Subject: [PATCH 04/88] 30093584 - Winning submission from Part 2 challenge --- .nvmrc | 2 +- config/custom-environment-variables.json | 3 +- config/default.json | 3 +- docs/Project API.postman_collection.json | 964 +++-- local/seed/projects.json | 402 +-- local/seed/seedMetadata.js | 20 +- local/seed/seedProjects.js | 48 +- migrations/elasticsearch_sync.js | 53 +- package-lock.json | 3176 +++++++++-------- src/constants.js | 95 +- src/events/busApi.js | 694 +--- src/events/index.js | 2 +- src/middlewares/fieldLookupValidation.js | 4 +- src/middlewares/validateMilestoneTemplate.js | 13 +- src/middlewares/validateTimeline.js | 7 +- src/models/timeline.js | 33 + src/routes/attachments/create.js | 58 +- src/routes/attachments/create.spec.js | 32 +- src/routes/attachments/delete.js | 11 +- src/routes/attachments/delete.spec.js | 24 +- src/routes/attachments/download.js | 79 +- src/routes/attachments/update.js | 26 +- src/routes/attachments/update.spec.js | 29 +- src/routes/form/revision/get.js | 2 +- src/routes/form/version/get.js | 2 +- src/routes/form/version/getVersion.js | 2 +- src/routes/milestoneTemplates/clone.js | 41 +- src/routes/milestoneTemplates/clone.spec.js | 42 +- src/routes/milestoneTemplates/create.js | 85 +- src/routes/milestoneTemplates/create.spec.js | 84 +- src/routes/milestoneTemplates/delete.js | 9 + src/routes/milestoneTemplates/get.js | 22 +- src/routes/milestoneTemplates/get.spec.js | 2 +- src/routes/milestoneTemplates/list.js | 79 +- src/routes/milestoneTemplates/list.spec.js | 10 +- src/routes/milestoneTemplates/update.js | 86 +- src/routes/milestoneTemplates/update.spec.js | 134 +- src/routes/milestones/create.js | 97 +- src/routes/milestones/create.spec.js | 205 +- src/routes/milestones/delete.js | 17 +- src/routes/milestones/delete.spec.js | 38 +- src/routes/milestones/get.js | 48 +- src/routes/milestones/get.spec.js | 2 +- src/routes/milestones/list.js | 24 +- src/routes/milestones/list.spec.js | 14 +- src/routes/milestones/update.js | 71 +- src/routes/milestones/update.spec.js | 215 +- src/routes/orgConfig/get.js | 2 +- src/routes/orgConfig/list.js | 5 +- src/routes/orgConfig/list.spec.js | 12 +- src/routes/phaseProducts/create.js | 36 +- src/routes/phaseProducts/create.spec.js | 26 +- src/routes/phaseProducts/delete.js | 10 +- src/routes/phaseProducts/delete.spec.js | 30 +- src/routes/phaseProducts/get.js | 73 +- src/routes/phaseProducts/get.spec.js | 4 +- src/routes/phaseProducts/list-db.js | 5 +- src/routes/phaseProducts/list-db.spec.js | 14 +- src/routes/phaseProducts/list.js | 85 +- src/routes/phaseProducts/list.spec.js | 14 +- src/routes/phaseProducts/update.js | 37 +- src/routes/phaseProducts/update.spec.js | 103 +- src/routes/phases/create.js | 70 +- src/routes/phases/create.spec.js | 63 +- src/routes/phases/delete.js | 12 +- src/routes/phases/delete.spec.js | 28 +- src/routes/phases/get.js | 62 +- src/routes/phases/get.spec.js | 4 +- src/routes/phases/list-db.js | 8 +- src/routes/phases/list-db.spec.js | 12 +- src/routes/phases/list.js | 46 +- src/routes/phases/list.spec.js | 12 +- src/routes/phases/update.js | 41 +- src/routes/phases/update.spec.js | 252 +- src/routes/planConfig/revision/get.js | 2 +- src/routes/planConfig/version/get.js | 2 +- src/routes/planConfig/version/getVersion.js | 2 +- src/routes/priceConfig/revision/get.js | 2 +- src/routes/priceConfig/version/get.js | 2 +- src/routes/priceConfig/version/getVersion.js | 2 +- src/routes/productCategories/get.js | 2 +- src/routes/productTemplates/get.js | 2 +- src/routes/productTemplates/list.js | 2 +- src/routes/productTemplates/list.spec.js | 15 +- src/routes/projectMemberInvites/create.js | 73 +- .../projectMemberInvites/create.spec.js | 182 +- src/routes/projectMemberInvites/get.js | 67 +- src/routes/projectMemberInvites/get.spec.js | 2 +- src/routes/projectMemberInvites/update.js | 25 +- .../projectMemberInvites/update.spec.js | 57 +- src/routes/projectMembers/create.js | 35 +- src/routes/projectMembers/create.spec.js | 24 +- src/routes/projectMembers/delete.js | 11 +- src/routes/projectMembers/delete.spec.js | 52 +- src/routes/projectMembers/update.js | 23 +- src/routes/projectMembers/update.spec.js | 80 +- src/routes/projectTemplates/get.js | 2 +- src/routes/projectTypes/get.js | 2 +- src/routes/projects/list-db.js | 17 +- src/routes/projects/list.js | 23 +- src/routes/timelines/create.js | 56 +- src/routes/timelines/create.spec.js | 164 +- src/routes/timelines/delete.js | 29 +- src/routes/timelines/get.js | 40 +- src/routes/timelines/get.spec.js | 2 +- src/routes/timelines/list.js | 46 +- src/routes/timelines/list.spec.js | 32 +- src/routes/timelines/update.js | 46 +- src/routes/timelines/update.spec.js | 165 +- src/services/busApi.js | 2 +- src/tests/seed.js | 4 + src/util.js | 129 +- 112 files changed, 4927 insertions(+), 4671 deletions(-) diff --git a/.nvmrc b/.nvmrc index a6105673..641c7df3 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v6.9.4 +v8.9.4 diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index c0edb2d3..48ad4d28 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -49,5 +49,6 @@ "connectUrl": "CONNECT_URL", "accountsAppUrl": "ACCOUNTS_APP_URL", "inviteEmailSubject": "INVITE_EMAIL_SUBJECT", - "inviteEmailSectionTitle": "INVITE_EMAIL_SECTION_TITLE" + "inviteEmailSectionTitle": "INVITE_EMAIL_SECTION_TITLE", + "pageSize": "PAGE_SIZE" } diff --git a/config/default.json b/config/default.json index bc2f0dc6..2e1248fc 100644 --- a/config/default.json +++ b/config/default.json @@ -55,5 +55,6 @@ "connectUrl":"https://connect.topcoder-dev.com", "accountsAppUrl": "https://accounts.topcoder-dev.com", "MAX_REVISION_NUMBER": 100, - "UNIQUE_GMAIL_VALIDATION": false + "UNIQUE_GMAIL_VALIDATION": false, + "pageSize": 20 } diff --git a/docs/Project API.postman_collection.json b/docs/Project API.postman_collection.json index 714891f5..5dfa7d14 100644 --- a/docs/Project API.postman_collection.json +++ b/docs/Project API.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "e76a2541-aafc-4944-92c5-e7bde5bf7930", + "_postman_id": "a47f8b92-7ae6-4e66-b8db-f5bcbe073ed9", "name": "Project API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -116,13 +116,13 @@ } ], "url": { - "raw": "{{api-url}}/projects/2", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "2" + "{{projectId}}" ] } }, @@ -144,16 +144,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name\",\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }]\n }\n}" + "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name\",\n \"bookmarks\":[{\n \"title\":\"title1\",\n \"address\":\"address1\"\n },{\n \"title\":\"title2\",\n \"address\":\"address2\"\n }]\n}" }, "url": { - "raw": "{{api-url}}/projects/2", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "2" + "{{projectId}}" ] } }, @@ -175,16 +175,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":null\n }\n}" + "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":null\n}" }, "url": { - "raw": "{{api-url}}/projects/2", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "2" + "{{projectId}}" ] } }, @@ -206,16 +206,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":3\n }\n}" + "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name2\",\n \"bookmarks\":3\n}" }, "url": { - "raw": "{{api-url}}/projects/2", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "2" + "{{projectId}}" ] } }, @@ -248,6 +248,21 @@ }, { "name": "Upload 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(\"attachmentId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -262,16 +277,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"title\": \"first attachment submission\",\n\t\t\"filePath\": \"asdjshdasdas/asdsadj/asdasd.png\",\n\t\t\"s3Bucket\": \"topcoder-project-service\",\n\t\t\"contentType\": \"application/png\"\n\t}\n}" + "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}" }, "url": { - "raw": "{{api-url}}/projects/7/attachments", + "raw": "{{api-url}}/projects/{{projectId}}/attachments", "host": [ "{{api-url}}" ], "path": [ "projects", - "7", + "{{projectId}}", "attachments" ] }, @@ -295,18 +310,18 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"title\": \"first attachment submission updated\",\n\t\t\"description\": \"updated project attachment\"\n\t}\n}" + "raw": "{\n\t\t\"title\": \"first attachment submission updated\",\n\t\t\"description\": \"updated project attachment\"\n\t}" }, "url": { - "raw": "{{api-url}}/projects/7/attachments/2", + "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{attachmentId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "7", + "{{projectId}}", "attachments", - "2" + "{{attachmentId}}" ] }, "description": "Update project attachment" @@ -332,15 +347,52 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/7/attachments/2", + "raw": "{{api-url}}/projects/{{projectId}}/attachments/{{attachmentId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "attachments", + "{{attachmentId}}" + ] + }, + "description": "Delete a project attachment" + }, + "response": [] + }, + { + "name": "Download attachment", + "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}}/attachments/{{attachmentId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "7", + "{{projectId}}", "attachments", - "2" + "{{attachmentId}}" ] }, "description": "Delete a project attachment" @@ -384,6 +436,21 @@ }, { "name": "Create project with templateId", + "event": [ + { + "listen": "test", + "script": { + "id": "ed52d7f8-829b-40ec-8583-7e8dbcc6741c", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -398,7 +465,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"test project with templateId\",\n \"description\": \"Hello I am a test project with templateId\",\n \"type\": \"generic\",\n \"templateId\": 3\n }" + "raw": "{\n \"name\": \"test project with templateId\",\n \"description\": \"Hello I am a test project with templateId\",\n \"type\": \"generic\",\n \"templateId\": {{projectTemplateId}}\n }" }, "url": { "raw": "{{api-url}}/projects", @@ -436,13 +503,13 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/1/members", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "members" ] }, @@ -466,16 +533,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\"param\":{\n\t\"role\": \"copilot\"\n}\n}" + "raw": "{\n\t\"role\": \"copilot\"\n}" }, "url": { - "raw": "{{api-url}}/projects/1/members", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "members" ] }, @@ -485,12 +552,27 @@ }, { "name": "Create project copilot with valid values", + "event": [ + { + "listen": "test", + "script": { + "id": "82218ddf-4bd4-485f-bcd4-1d653c674680", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"memberId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer {{jwt-token-copilot-40051332}}" }, { "key": "Content-Type", @@ -499,16 +581,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"userId\": 40051331,\n\t\t\"isPrimary\": true\n\t}\n}" + "raw": "{\n\t\"role\": \"copilot\"\n}" }, "url": { - "raw": "{{api-url}}/projects/7/members", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "7", + "{{projectId}}", "members" ] }, @@ -532,16 +614,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"userId\": 40051331,\n\t\t\"isPrimary\": true\n\t}\n}" + "raw": "{\n\t\t\"role\": \"copilot\"\n}" }, "url": { - "raw": "{{api-url}}/projects/1/members", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "members" ] }, @@ -551,6 +633,21 @@ }, { "name": "Create project manager with valid values", + "event": [ + { + "listen": "test", + "script": { + "id": "ba7b3265-aaba-4cca-8a53-819c9e96cfae", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"memberId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -565,16 +662,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"role\": \"manager\",\n\t\t\"userId\": 40051330,\n\t\t\"isPrimary\": true\n\t}\n}" + "raw": "{\n\t\"role\": \"manager\"\n}" }, "url": { - "raw": "{{api-url}}/projects/7/members", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "7", + "{{projectId}}", "members" ] }, @@ -584,6 +681,21 @@ }, { "name": "Create project customer with valid values", + "event": [ + { + "listen": "test", + "script": { + "id": "87ab173c-8a4b-4d12-bfe7-ebcd272289f1", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"memberId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -598,16 +710,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"role\": \"customer\",\n\t\t\"userId\": 40051332,\n\t\t\"isPrimary\": true\n\t}\n}" + "raw": "{\n\t\t\"role\": \"copilot\"\n}" }, "url": { - "raw": "{{api-url}}/projects/7/members", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "7", + "{{projectId}}", "members" ] }, @@ -631,18 +743,18 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"isPrimary\": true\n\t}\n}" + "raw": "{\n\t\"role\": \"copilot\",\n\t\"isPrimary\": true\n}" }, "url": { - "raw": "{{api-url}}/projects/1/members/1", + "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "members", - "1" + "{{memberId}}" ] }, "description": "Update a project's member." @@ -665,18 +777,18 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"role\": \"copilot\",\n\t\t\"isPrimary\": false\n\t}\n}" + "raw": "{\n\t\"role\": \"copilot\",\n\t\"isPrimary\": false\n}" }, "url": { - "raw": "{{api-url}}/projects/1/members/1", + "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "members", - "1" + "{{memberId}}" ] }, "description": "Update a project's member." @@ -699,18 +811,18 @@ ], "body": { "mode": "raw", - "raw": " {\n \"param\": {\n \"role\": \"wrong\"\n }\n } " + "raw": " {\n\t\"role\": \"wrong\"\n } " }, "url": { - "raw": "{{api-url}}/projects/3/members/5", + "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "3", + "{{projectId}}", "members", - "5" + "{{memberId}}" ] } }, @@ -735,15 +847,15 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/3/members/5", + "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "3", + "{{projectId}}", "members", - "5" + "{{memberId}}" ] }, "description": "Delete a project's member" @@ -766,18 +878,18 @@ ], "body": { "mode": "raw", - "raw": " {\n \"param\": {\n \"role\": \"manager\",\n \"isPrimary\": true\n }\n } " + "raw": " {\n \"role\": \"manager\",\n \"isPrimary\": true\n } " }, "url": { - "raw": "{{api-url}}/projects/1/members/2", + "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "members", - "2" + "{{memberId}}" ] } }, @@ -785,6 +897,205 @@ } ] }, + { + "name": "Project Members Invites", + "item": [ + { + "name": "Create project member with no payload", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/members/invite", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "members", + "invite" + ] + }, + "description": "Request payload is mandatory while creating project. If no request payload is specified this should result in 400 status code." + }, + "response": [] + }, + { + "name": "Create project customer with valid values", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"role\": \"customer\",\n\t\"emails\": [\"test@topcoder.com\"]\n}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/members/invite", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "members", + "invite" + ] + }, + "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": "Get 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}}/members/invite", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "members", + "invite" + ] + }, + "description": "Update a project's member." + }, + "response": [] + }, + { + "name": "Update project member invite", + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"status\": \"accepted\",\n\t\"email\": \"test@topcoder.com\"\n}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/members/invite", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "members", + "invite" + ] + }, + "description": "Update a project's member." + }, + "response": [] + }, + { + "name": "wrong status", + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": " {\n\t\"status\": \"wrong\"\n } " + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/members/invite", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "members", + "invite" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "522949a9-3d94-4103-92b2-976af332f203", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "df2755ee-59a5-4d8d-a6ad-6416b697c894", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, { "name": "Projects", "item": [ @@ -881,7 +1192,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n\t}" + "raw": "{\n\t\"name\": \"test project\",\n\t\"description\": \"Hello I am a test project\",\n\t\"type\": \"{{projectTypeId}}\"\n}" }, "url": { "raw": "{{api-url}}/projects", @@ -912,7 +1223,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n\t}" + "raw": "{\n\t\"name\": \"test project\",\n\t\"description\": \"Hello I am a test project\",\n\t\"type\": \"{{projectTypeId}}\"\n}" }, "url": { "raw": "{{api-url}}/projects", @@ -1006,7 +1317,7 @@ "response": [] }, { - "name": "List projects DB with limit and offset", + "name": "List projects DB with page and perPage", "request": { "method": "GET", "header": [ @@ -1016,7 +1327,7 @@ } ], "url": { - "raw": "{{api-url}}/projects/db?limit=1&offset=1", + "raw": "{{api-url}}/projects/db?perPage=1&page=1", "host": [ "{{api-url}}" ], @@ -1026,11 +1337,11 @@ ], "query": [ { - "key": "limit", + "key": "perPage", "value": "1" }, { - "key": "offset", + "key": "page", "value": "1" } ] @@ -1114,7 +1425,7 @@ } ], "url": { - "raw": "{{api-url}}/projects/db?sort=type%20desc", + "raw": "{{api-url}}/projects/db?sort=type desc", "host": [ "{{api-url}}" ], @@ -1125,7 +1436,7 @@ "query": [ { "key": "sort", - "value": "type%20desc" + "value": "type desc" } ] }, @@ -1210,7 +1521,7 @@ "response": [] }, { - "name": "List projects with limit and offset", + "name": "List projects with perPage and page", "request": { "method": "GET", "header": [ @@ -1220,7 +1531,7 @@ } ], "url": { - "raw": "{{api-url}}/projects?limit=1&offset=1", + "raw": "{{api-url}}/projects?perPage=1&page=1", "host": [ "{{api-url}}" ], @@ -1229,11 +1540,11 @@ ], "query": [ { - "key": "limit", + "key": "perPage", "value": "1" }, { - "key": "offset", + "key": "page", "value": "1" } ] @@ -1428,7 +1739,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"name\": \"project name updated\"\n }" + "raw": "{\n \"name\": \"project name updated\"\n}" }, "url": { "raw": "{{api-url}}/projects/{{projectId}}", @@ -2004,16 +2315,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}" + "raw": "{\n\n \"role\": \"copilot\"\n}" }, "url": { - "raw": "{{api-url}}/projects/1/members", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "members" ] } @@ -2036,16 +2347,16 @@ ], "body": { "mode": "raw", - "raw": "{\n \"param\": {\n \"userId\": 2, \n \"role\": \"copilot\"\n }\n}" + "raw": "{\n\t\"role\": \"copilot\"\n}" }, "url": { - "raw": "{{api-url}}/projects/2/members", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "2", + "{{projectId}}", "members" ] } @@ -2068,18 +2379,18 @@ ], "body": { "mode": "raw", - "raw": " {\n \"param\": {\n \"role\": \"customer\",\n \"isPrimary\": true\n }\n } " + "raw": " {\n \"role\": \"customer\",\n \"isPrimary\": true\n } " }, "url": { - "raw": "{{api-url}}/projects/2/members/4", + "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "2", + "{{projectId}}", "members", - "4" + "{{memberId}}" ] } }, @@ -2104,13 +2415,13 @@ "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }" }, "url": { - "raw": "{{api-url}}/projects/2", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "2" + "{{projectId}}" ] } }, @@ -2135,15 +2446,15 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/2/members/4", + "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "2", + "{{projectId}}", "members", - "4" + "{{memberId}}" ] } }, @@ -2178,6 +2489,21 @@ "item": [ { "name": "Create Phase", + "event": [ + { + "listen": "test", + "script": { + "id": "7050133a-b934-4faf-8101-d2e80b5c0710", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"phaseId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -2192,16 +2518,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t}\n\t}\n}" + "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t}\n}" }, "url": { - "raw": "{{api-url}}/projects/1/phases", + "raw": "{{api-url}}/projects/{{projectId}}/phases", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases" ] } @@ -2210,6 +2536,21 @@ }, { "name": "Create Phase with order", + "event": [ + { + "listen": "test", + "script": { + "id": "2f771afe-7b4e-4260-b04d-324e880eb61b", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"phaseId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -2224,16 +2565,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t},\n\t\t\"order\": 1\n\t}\n}" + "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t},\n\t\"order\": 1\n}" }, "url": { - "raw": "{{api-url}}/projects/1/phases", + "raw": "{{api-url}}/projects/{{projectId}}/phases", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases" ] } @@ -2242,6 +2583,21 @@ }, { "name": "Create Phase with productTemplateId", + "event": [ + { + "listen": "test", + "script": { + "id": "8415ad98-b3f6-4330-88b6-e1830da2e4f9", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"phaseId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -2256,16 +2612,16 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t},\n\t\t\"order\": 1,\n\t\t\"productTemplateId\": 1\n\t}\n}" + "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t},\n\t\"order\": 1,\n\t\"productTemplateId\": {{productTemplateId}}\n}" }, "url": { - "raw": "{{api-url}}/projects/1/phases", + "raw": "{{api-url}}/projects/{{projectId}}/phases", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases" ] } @@ -2287,13 +2643,13 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases/db", + "raw": "{{api-url}}/projects/{{projectId}}/phases/db", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", "db" ] @@ -2316,13 +2672,13 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases/db?fields=status,name,budget", + "raw": "{{api-url}}/projects/{{projectId}}/phases/db?fields=status,name,budget", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", "db" ], @@ -2351,13 +2707,13 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases/db?sort=status desc", + "raw": "{{api-url}}/projects/{{projectId}}/phases/db?sort=status desc", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", "db" ], @@ -2386,13 +2742,13 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases/db?sort=order desc", + "raw": "{{api-url}}/projects/{{projectId}}/phases/db?sort=order desc", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", "db" ], @@ -2421,13 +2777,13 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases", + "raw": "{{api-url}}/projects/{{projectId}}/phases", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases" ] } @@ -2449,13 +2805,13 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases?fields=status,name,budget", + "raw": "{{api-url}}/projects/{{projectId}}/phases?fields=status,name,budget", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases" ], "query": [ @@ -2483,13 +2839,13 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases?sort=status desc", + "raw": "{{api-url}}/projects/{{projectId}}/phases?sort=status desc", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases" ], "query": [ @@ -2517,13 +2873,13 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases?sort=order desc", + "raw": "{{api-url}}/projects/{{projectId}}/phases?sort=order desc", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases" ], "query": [ @@ -2551,15 +2907,15 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases/1", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "1" + "{{phaseId}}" ] } }, @@ -2581,18 +2937,18 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase xxx\",\n\t\t\"status\": \"inactive\",\n\t\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\t\"budget\": 30,\n\t\t\"progress\": 15,\n\t\t\"details\": {\n\t\t\t\"message\": \"phase details\"\n\t\t}\n\t}\n}" + "raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t}\n}" }, "url": { - "raw": "{{api-url}}/projects/1/phases/1", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "1" + "{{phaseId}}" ] } }, @@ -2614,18 +2970,18 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test project phase xxx\",\n\t\t\"status\": \"inactive\",\n\t\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\t\"budget\": 30,\n\t\t\"progress\": 15,\n\t\t\"details\": {\n\t\t\t\"message\": \"phase details\"\n\t\t},\n\t\t\"order\": 1\n\t}\n}" + "raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t},\n\t\"order\": 1\n}" }, "url": { - "raw": "{{api-url}}/projects/1/phases/1", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "1" + "{{phaseId}}" ] } }, @@ -2650,15 +3006,15 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/1/phases/3", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "3" + "{{phaseId}}" ] } }, @@ -2671,6 +3027,21 @@ "item": [ { "name": "Create Phase Product", + "event": [ + { + "listen": "test", + "script": { + "id": "77f089b3-cbe6-4fb4-b54f-2a52d138a050", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"phaseProductId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -2685,18 +3056,18 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test phase product\",\n\t\t\"type\": \"type 1\",\n\t\t\"estimatedPrice\": 10\n\t}\n}" + "raw": "{\n\t\"name\": \"test phase product\",\n\t\"type\": \"type 1\",\n\t\"estimatedPrice\": 10\n}" }, "url": { - "raw": "{{api-url}}/projects/1/phases/1/products", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "1", + "{{phaseId}}", "products" ] } @@ -2714,15 +3085,15 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases/1/products/db", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/db", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "1", + "{{phaseId}}", "products", "db" ] @@ -2741,15 +3112,15 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases/1/products", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "1", + "{{phaseId}}", "products" ] } @@ -2767,17 +3138,17 @@ } ], "url": { - "raw": "{{api-url}}/projects/1/phases/1/products/1", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "1", + "{{phaseId}}", "products", - "1" + "{{phaseProductId}}" ] } }, @@ -2799,20 +3170,20 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"param\": {\n\t\t\"name\": \"test phase product xxx\",\n\t\t\"type\": \"type 2\",\n\t\t\"templateId\": 10,\n\t\t\"estimatedPrice\": 1.234567,\n\t\t\"actualPrice\": 2.34567,\n\t\t\"details\": {\n\t\t\t\"message\": \"this is a JSON type. You can use any json\"\n\t\t}\n\t}\n}" + "raw": "{\n\t\"name\": \"test phase product xxx\",\n\t\"type\": \"type 2\",\n\t\"templateId\": 10,\n\t\"estimatedPrice\": 1.234567,\n\t\"actualPrice\": 2.34567,\n\t\"details\": {\n\t\t\"message\": \"this is a JSON type. You can use any json\"\n\t}\n}" }, "url": { - "raw": "{{api-url}}/projects/1/phases/1/products/1", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "1", + "{{phaseId}}", "products", - "1" + "{{phaseProductId}}" ] } }, @@ -2833,17 +3204,17 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/1/phases/1/products/1", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "1", + "{{projectId}}", "phases", - "1", + "{{phaseId}}", "products", - "1" + "{{phaseProductId}}" ] } }, @@ -3200,7 +3571,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{ \r\n }\r\n}" + "raw": "{\r\n}" }, "url": { "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", @@ -3386,7 +3757,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"subCategory\": \"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"template\": {\r\n \"template1\": {\r\n \"name\": \"template 1\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1\"\r\n },\r\n \"others\": [\"others 11\", \"others 12\"]\r\n },\r\n \"template2\": {\r\n \"name\": \"template 2\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 2\"\r\n },\r\n \"others\": [\"others 21\", \"others 22\"]\r\n }\r\n }\r\n }" + "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"{{productCategoryId}}\",\r\n \"subCategory\": \"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"template\": {\r\n \"template1\": {\r\n \"name\": \"template 1\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1\"\r\n },\r\n \"others\": [\"others 11\", \"others 12\"]\r\n },\r\n \"template2\": {\r\n \"name\": \"template 2\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 2\"\r\n },\r\n \"others\": [\"others 21\", \"others 22\"]\r\n }\r\n }\r\n }" }, "url": { "raw": "{{api-url}}/projects/metadata/productTemplates", @@ -4267,6 +4638,21 @@ "item": [ { "name": "Create timeline", + "event": [ + { + "listen": "test", + "script": { + "id": "c066e7d4-537f-406e-a768-ec4bf73a2634", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"timelineId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -4281,7 +4667,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}" + "raw": "{\r\n\t\"name\":\"new name\",\r\n\t\"description\":\"new description\",\r\n\t\"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n\t\"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n\t\"reference\": \"project\",\r\n\t\"referenceId\": {{projectId}}\r\n}" }, "url": { "raw": "{{api-url}}/timelines", @@ -4297,6 +4683,21 @@ }, { "name": "Create timeline with templateId", + "event": [ + { + "listen": "test", + "script": { + "id": "ee729ed9-0072-4821-9141-3615ff66f728", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"timelineId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -4311,7 +4712,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1,\r\n \"templateId\": 1\r\n }\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1,\r\n \"templateId\": 1\r\n}" }, "url": { "raw": "{{api-url}}/timelines", @@ -4341,7 +4742,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-28T00:00:00.000Z\",\r\n \"reference\": \"invalid\",\r\n \"referenceId\": 0\r\n }\r\n}" + "raw": "{\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-28T00:00:00.000Z\",\r\n \"reference\": \"invalid\",\r\n \"referenceId\": 0\r\n}" }, "url": { "raw": "{{api-url}}/timelines", @@ -4366,11 +4767,17 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-copilot-40051332}}" + "value": "Bearer {{jwt-token-copilot-40051332}}", + "disabled": true + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}", + "type": "text" } ], "url": { - "raw": "{{api-url}}/timelines?filter=reference%3Dphase%26referenceId%3D1", + "raw": "{{api-url}}/timelines?reference=project&referenceId={{projectId}}", "host": [ "{{api-url}}" ], @@ -4379,8 +4786,12 @@ ], "query": [ { - "key": "filter", - "value": "reference%3Dphase%26referenceId%3D1" + "key": "reference", + "value": "project" + }, + { + "key": "referenceId", + "value": "{{projectId}}" } ] } @@ -4402,13 +4813,13 @@ } ], "url": { - "raw": "{{api-url}}/timelines/1", + "raw": "{{api-url}}/timelines/{{timelineId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1" + "{{timelineId}}" ] } }, @@ -4430,16 +4841,16 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-01T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}" + "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-01T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": {{projectId}}\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1", + "raw": "{{api-url}}/timelines/{{timelineId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1" + "{{timelineId}}" ] } }, @@ -4461,16 +4872,16 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}" + "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1", + "raw": "{{api-url}}/timelines/{{timelineId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1" + "{{timelineId}}" ] } }, @@ -4492,16 +4903,16 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n }\r\n}" + "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1", + "raw": "{{api-url}}/timelines/{{timelineId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1" + "{{timelineId}}" ] } }, @@ -4526,13 +4937,13 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/timelines/4", + "raw": "{{api-url}}/timelines/{{timelineId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "4" + "{{timelineId}}" ] } }, @@ -4545,6 +4956,21 @@ "item": [ { "name": "Create milestone", + "event": [ + { + "listen": "test", + "script": { + "id": "8fd1d5e9-8e6e-4cd7-9010-b855308be069", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"milestoneId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -4554,21 +4980,21 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-member-40051331}}" + "value": "Bearer {{jwt-token-admin-40051333}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 4,\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-08T00:00:00.000Z\",\r\n \"status\": \"open\",\r\n \"type\": \"type3\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 2,\r\n 3,\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 3\",\r\n \"activeText\": \"activeText 3\",\r\n \"completedText\": \"completedText 3\",\r\n \"blockedText\": \"blockedText 3\"\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestone 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 4,\r\n \"startDate\": \"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-31T00:00:00.000Z\",\r\n \"status\": \"open\",\r\n \"type\": \"type3\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 2,\r\n 3,\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 3\",\r\n \"activeText\": \"activeText 3\",\r\n \"completedText\": \"completedText 3\",\r\n \"blockedText\": \"blockedText 3\"\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones" ] } @@ -4591,16 +5017,16 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-04T00:00:00.000Z\"\r\n }\r\n}" + "raw": "{\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-04T00:00:00.000Z\"\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones" ] } @@ -4618,17 +5044,18 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-copilot-40051332}}" + "value": "Bearer {{jwt-token}}", + "type": "text" } ], "url": { - "raw": "{{api-url}}/timelines/1/milestones", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones" ] } @@ -4650,13 +5077,13 @@ } ], "url": { - "raw": "{{api-url}}/timelines/1/milestones?sort=order desc", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones?sort=order desc", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones" ], "query": [ @@ -4684,15 +5111,15 @@ } ], "url": { - "raw": "{{api-url}}/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones", - "1" + "{{milestoneId}}" ] } }, @@ -4714,18 +5141,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-09-28T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-09-28T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones", - "1" + "{{milestoneId}}" ] } }, @@ -4747,18 +5174,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"active\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"active\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones/2", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones", - "2" + "{{milestoneId}}" ] } }, @@ -4780,18 +5207,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"completed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"completed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones/2", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones", - "2" + "{{milestoneId}}" ] } }, @@ -4813,18 +5240,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones", - "1" + "{{milestoneId}}" ] } }, @@ -4846,18 +5273,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones", - "1" + "{{milestoneId}}" ] } }, @@ -4879,18 +5306,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones", - "1" + "{{milestoneId}}" ] } }, @@ -4912,18 +5339,18 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones/1", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones", - "1" + "{{milestoneId}}" ] } }, @@ -4948,15 +5375,15 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/timelines/1/milestones/2", + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", "host": [ "{{api-url}}" ], "path": [ "timelines", - "1", + "{{timelineId}}", "milestones", - "2" + "{{milestoneId}}" ] } }, @@ -4969,6 +5396,21 @@ "item": [ { "name": "Create milestone template", + "event": [ + { + "listen": "test", + "script": { + "id": "3dbf8b29-2498-4b05-93de-14d809ccc285", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"milestoneTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -4983,7 +5425,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {}\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestoneTemplate 1\",\r\n \"description\": \"description 1\",\r\n \"duration\": 11,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": {{productTemplateId}},\r\n\t\"metadata\": {}\r\n}" }, "url": { "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", @@ -5015,7 +5457,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1000,\r\n\t\"metadata\": {}\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1000,\r\n\t\"metadata\": {}\r\n}" }, "url": { "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", @@ -5047,7 +5489,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n\r\n }\r\n}" + "raw": "{\r\n}" }, "url": { "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", @@ -5079,7 +5521,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n }\r\n}" + "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n}" }, "url": { "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", @@ -5112,7 +5554,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2000\r\n }\r\n}" + "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2000\r\n}" }, "url": { "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", @@ -5145,7 +5587,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1000,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n }\r\n}" + "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1000,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n}" }, "url": { "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", @@ -5205,7 +5647,7 @@ } ], "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId={{productTemplateId}}", "host": [ "{{api-url}}" ], @@ -5216,8 +5658,12 @@ ], "query": [ { - "key": "filter", - "value": "reference%3DproductTemplate%26referenceId%3D1" + "key": "reference", + "value": "productTemplate" + }, + { + "key": "referenceId", + "value": "{{productTemplateId}}" } ] } @@ -5239,7 +5685,7 @@ } ], "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1&sort=order desc", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId={{productTemplateId}}&sort=order desc", "host": [ "{{api-url}}" ], @@ -5250,8 +5696,12 @@ ], "query": [ { - "key": "filter", - "value": "reference%3DproductTemplate%26referenceId%3D1" + "key": "reference", + "value": "productTemplate" + }, + { + "key": "referenceId", + "value": "{{productTemplateId}}" }, { "key": "sort", @@ -5277,7 +5727,7 @@ } ], "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", "host": [ "{{api-url}}" ], @@ -5285,7 +5735,7 @@ "timelines", "metadata", "milestoneTemplates", - "1" + "{{milestoneTemplateId}}" ] } }, @@ -5307,10 +5757,10 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" + "raw": "{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", "host": [ "{{api-url}}" ], @@ -5318,7 +5768,7 @@ "timelines", "metadata", "milestoneTemplates", - "1" + "{{milestoneTemplateId}}" ] } }, @@ -5340,10 +5790,10 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 2,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 2,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": {{productTemplateId}}\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", "host": [ "{{api-url}}" ], @@ -5351,7 +5801,7 @@ "timelines", "metadata", "milestoneTemplates", - "1" + "{{milestoneTemplateId}}" ] } }, @@ -5373,10 +5823,10 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" + "raw": "{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", "host": [ "{{api-url}}" ], @@ -5384,7 +5834,7 @@ "timelines", "metadata", "milestoneTemplates", - "1" + "{{milestoneTemplateId}}" ] } }, @@ -5406,10 +5856,10 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 3,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 3,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", "host": [ "{{api-url}}" ], @@ -5417,7 +5867,7 @@ "timelines", "metadata", "milestoneTemplates", - "1" + "{{milestoneTemplateId}}" ] } }, @@ -5439,10 +5889,10 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n }\r\n}" + "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", "host": [ "{{api-url}}" ], @@ -5450,7 +5900,7 @@ "timelines", "metadata", "milestoneTemplates", - "1" + "{{milestoneTemplateId}}" ] } }, @@ -5472,10 +5922,10 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n\t\"name\": \"milestoneTemplate 5-updated\",\r\n\t\"description\": \"description 5-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type5-updated\",\r\n\t\"order\": 5,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {\r\n \"metadata1\": {\r\n \"name\": \"metadata 1 - update\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1 - update\",\r\n \"newDetails\": \"new\"\r\n },\r\n \"others\": [\"others new\"]\r\n },\r\n \"metadata3\": {\r\n \"name\": \"metadata 3\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 3\"\r\n },\r\n \"others\": [\"others 31\", \"others 32\"]\r\n }\r\n }\r\n }\r\n}" + "raw": "{\r\n\t\"name\": \"milestoneTemplate 5-updated\",\r\n\t\"description\": \"description 5-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type5-updated\",\r\n\t\"order\": 5,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {\r\n \"metadata1\": {\r\n \"name\": \"metadata 1 - update\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1 - update\",\r\n \"newDetails\": \"new\"\r\n },\r\n \"others\": [\"others new\"]\r\n },\r\n \"metadata3\": {\r\n \"name\": \"metadata 3\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 3\"\r\n },\r\n \"others\": [\"others 31\", \"others 32\"]\r\n }\r\n }\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/1", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", "host": [ "{{api-url}}" ], @@ -5483,7 +5933,7 @@ "timelines", "metadata", "milestoneTemplates", - "1" + "{{milestoneTemplateId}}" ] } }, @@ -5508,7 +5958,7 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/2", + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", "host": [ "{{api-url}}" ], @@ -5516,7 +5966,7 @@ "timelines", "metadata", "milestoneTemplates", - "2" + "{{milestoneTemplateId}}" ] } }, @@ -5616,7 +6066,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/form/dev/versions", + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions", "host": [ "{{api-url}}" ], @@ -5624,7 +6074,7 @@ "projects", "metadata", "form", - "dev", + "{{formKey}}", "versions" ] } @@ -5858,7 +6308,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/form/dev/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions", "host": [ "{{api-url}}" ], @@ -5866,9 +6316,9 @@ "projects", "metadata", "form", - "dev", + "{{formKey}}", "versions", - "1", + "{{formVersion}}", "revisions" ] } diff --git a/local/seed/projects.json b/local/seed/projects.json index 9fb184af..5e76d182 100644 --- a/local/seed/projects.json +++ b/local/seed/projects.json @@ -1,264 +1,242 @@ [ { - "param": { - "name": "Develop app", - "details": { - "utm": { - "code": "R&D" + "name": "Develop app", + "details": { + "utm": { + "code": "R&D" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 1, - "type": "app", - "status": "draft" - } + "hideDiscussions": true + }, + "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", + "templateId": 1, + "type": "app", + "status": "draft" }, { - "param": { - "name": "Develop website", - "details": { - "utm": { - "code": "" + "name": "Develop website", + "details": { + "utm": { + "code": "" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 2, - "type": "chatbot", - "status": "in_review" - } + "hideDiscussions": true + }, + "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", + "templateId": 2, + "type": "chatbot", + "status": "in_review" }, { - "param": { - "name": "Develop website 2", - "details": { - "utm": { - "code": "" + "name": "Develop website 2", + "details": { + "utm": { + "code": "" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 3, - "type": "website", - "status": "reviewed" - } + "hideDiscussions": true + }, + "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", + "templateId": 3, + "type": "website", + "status": "reviewed" }, { - "param": { - "name": "Develop chatbot", - "details": { - "utm": { - "code": "" + "name": "Develop chatbot", + "details": { + "utm": { + "code": "" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 4, - "type": "chatbot", - "status": "active" - } + "hideDiscussions": true + }, + "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", + "templateId": 4, + "type": "chatbot", + "status": "active" }, { - "param": { - "name": "Develop app 2", - "details": { - "utm": { - "code": "" + "name": "Develop app 2", + "details": { + "utm": { + "code": "" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 1, - "type": "app", - "status": "completed" - } + "hideDiscussions": true + }, + "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", + "templateId": 1, + "type": "app", + "status": "completed" }, { - "param": { - "name": "Develop website 3", - "details": { - "utm": { - "code": "" + "name": "Develop website 3", + "details": { + "utm": { + "code": "" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 2, - "type": "website", - "status": "paused" - } + "hideDiscussions": true + }, + "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", + "templateId": 2, + "type": "website", + "status": "paused" }, { - "param": { - "name": "Develop app 3", - "details": { - "utm": { - "code": "" + "name": "Develop app 3", + "details": { + "utm": { + "code": "" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 1, - "type": "app", - "status": "cancelled", - "cancelReason": "Test cancel" - } + "hideDiscussions": true + }, + "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", + "templateId": 1, + "type": "app", + "status": "cancelled", + "cancelReason": "Test cancel" }, { - "param": { - "name": "Reviewed project with copilot invited", - "details": { - "utm": { - "code": "" + "name": "Reviewed project with copilot invited", + "details": { + "utm": { + "code": "" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 3, - "type": "website", - "status": "reviewed", - "invites": [{ - "param": { - "userIds": [40152855], - "role": "copilot" - }}] - } + "hideDiscussions": true + }, + "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", + "templateId": 3, + "type": "website", + "status": "reviewed", + "invites": [{ + "userIds": [40152855], + "role": "copilot" + }] }, { - "param": { - "name": "Reviewed project with copilot as a member with copilot role", - "details": { - "utm": { - "code": "" + "name": "Reviewed project with copilot as a member with copilot role", + "details": { + "utm": { + "code": "" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 3, - "type": "website", - "status": "reviewed", - "invites": [{ - "param": { - "userIds": [40152855], - "role": "copilot" - }}], - "acceptInvitation": true - } + "hideDiscussions": true + }, + "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", + "templateId": 3, + "type": "website", + "status": "reviewed", + "invites": [{ + "userIds": [40152855], + "role": "copilot" + }], + "acceptInvitation": true }, { - "param": { - "name": "Reviewed project when copilot is not a member and not invited", - "details": { - "utm": { - "code": "" + "name": "Reviewed project when copilot is not a member and not invited", + "details": { + "utm": { + "code": "" + }, + "appDefinition": { + "primaryTarget": "phone", + "goal": { + "value": "Nothing" }, - "appDefinition": { - "primaryTarget": "phone", - "goal": { - "value": "Nothing" - }, - "users": { - "value": "No one" - }, - "notes": "" + "users": { + "value": "No one" }, - "hideDiscussions": true + "notes": "" }, - "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", - "templateId": 3, - "type": "website", - "status": "reviewed" - } + "hideDiscussions": true + }, + "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", + "templateId": 3, + "type": "website", + "status": "reviewed" } ] diff --git a/local/seed/seedMetadata.js b/local/seed/seedMetadata.js index eca3aa20..6cf6cfa8 100644 --- a/local/seed/seedMetadata.js +++ b/local/seed/seedMetadata.js @@ -39,9 +39,9 @@ module.exports = (targetUrl, token) => { let promises = _(data.result.content.projectTypes).map(pt=>{ return axios - .post(destUrl+'metadata/projectTypes',{param:pt}, {headers:headers}) + .post(destUrl+'metadata/projectTypes', pt, {headers:headers}) .catch((err) => { - const errMessage = _.get(err, 'response.data.result.content.message', ''); + const errMessage = _.get(err, 'response.data.message', ''); console.log(`Failed to create projectType with key=${pt.key}.`, errMessage) }) }); @@ -50,9 +50,9 @@ module.exports = (targetUrl, token) => { promises = _(data.result.content.productCategories).map(pt=>{ return axios - .post(destUrl+'metadata/productCategories',{param:pt}, {headers:headers}) + .post(destUrl+'metadata/productCategories', pt, {headers:headers}) .catch((err) => { - const errMessage = _.get(err, 'response.data.result.content.message', ''); + const errMessage = _.get(err, 'response.data.message', ''); console.log(`Failed to create productCategory with key=${pt.key}.`, errMessage) }) }); @@ -61,9 +61,9 @@ module.exports = (targetUrl, token) => { promises = _(data.result.content.projectTemplates).map(pt=>{ return axios - .post(destUrl+'metadata/projectTemplates',{param:pt}, {headers:headers}) + .post(destUrl+'metadata/projectTemplates', pt, {headers:headers}) .catch((err) => { - const errMessage = _.get(err, 'response.data.result.content.message', ''); + const errMessage = _.get(err, 'response.data.message', ''); console.log(`Failed to create projectTemplate with id=${pt.id}.`, errMessage) }) }); @@ -72,9 +72,9 @@ module.exports = (targetUrl, token) => { promises = _(data.result.content.productTemplates).map(pt=>{ return axios - .post(destUrl+'metadata/productTemplates',{param:pt}, {headers:headers}) + .post(destUrl+'metadata/productTemplates', pt, {headers:headers}) .catch((err) => { - const errMessage = _.get(err, 'response.data.result.content.message', ''); + const errMessage = _.get(err, 'response.data.message', ''); console.log(`Failed to create productTemplate with id=${pt.id}.`, errMessage) }) }); @@ -83,9 +83,9 @@ module.exports = (targetUrl, token) => { await Promise.each(data.result.content.milestoneTemplates,pt=> ( axios - .post(destTimelines+'timelines/metadata/milestoneTemplates',{param:pt}, {headers:headers}) + .post(destTimelines+'timelines/metadata/milestoneTemplates', pt, {headers:headers}) .catch((err) => { - const errMessage = _.get(err, 'response.data.result.content.message', ''); + const errMessage = _.get(err, 'response.data.message', ''); console.log(`Failed to create milestoneTemplate with id=${pt.id}.`, errMessage) }) )); diff --git a/local/seed/seedProjects.js b/local/seed/seedProjects.js index e1d50417..94677587 100644 --- a/local/seed/seedProjects.js +++ b/local/seed/seedProjects.js @@ -26,16 +26,22 @@ module.exports = (targetUrl, token) => { }; console.log('Creating projects'); - projectPromises = projects.map((project, i) => { - const status = _.get(project, 'param.status'); - const cancelReason = _.get(project, 'param.cancelReason'); - const invites = _.cloneDeep(_.get(project, 'param.invites')); - const acceptInvitation = _.get(project, 'param.acceptInvitation'); + projectPromises = projects.map(async (project, i) => { + const status = _.get(project, 'status'); + const cancelReason = _.get(project, 'cancelReason'); + const invites = _.cloneDeep(_.get(project, 'invites')); + const acceptInvitation = _.get(project, 'acceptInvitation'); + + if(project.templateId) { + await findProjectTemplate(project.templateId, targetUrl, adminHeaders).catch((ex) => { + delete project.templateId; + }); + } - delete project.param.status; - delete project.param.cancelReason; - delete project.param.invites; - delete project.param.acceptInvitation; + delete project.status; + delete project.cancelReason; + delete project.invites; + delete project.acceptInvitation; return axios .post(projectsUrl, project, { headers: adminHeaders }) @@ -43,10 +49,10 @@ module.exports = (targetUrl, token) => { console.log(`Failed to create project ${i}: ${err.message}`); }) .then(async (response) => { - const projectId = _.get(response, 'data.result.content.id'); + const projectId = _.get(response, 'data.id'); // updating status - if (status !== _.get(response, 'data.result.content.status')) { + if (status !== _.get(response, 'data.status')) { console.log(`Project #${projectId}: Wait a bit to give time ES to index before updating status...`); await Promise.delay(ES_INDEX_DELAY); await updateProjectStatus(projectId, { status, cancelReason }, targetUrl, adminHeaders).catch((ex) => { @@ -68,12 +74,10 @@ module.exports = (targetUrl, token) => { if (acceptInvitation) { let acceptInvitationPromises = [] responses.forEach(response => { - const userId = _.get(response, 'data.result.content.success[0].userId') + const userId = _.get(response, 'data.success[0].userId') acceptInvitationPromises.push(updateProjectMemberInvite(projectId, { - param: { - userId, - status: 'accepted' - } + userId, + status: 'accepted' }, targetUrl, connectAdminHeaders)) }) @@ -106,9 +110,7 @@ function updateProjectStatus(project, updateParams, targetUrl, headers) { return axios.patch( projectUpdateUrl, - { - param: updateParams, - }, + updateParams, { headers, }, @@ -135,3 +137,11 @@ function updateProjectMemberInvite(projectId, params, targetUrl, headers) { }) } +function findProjectTemplate(templateId, targetUrl, headers) { + const projectTemplateUrl = `${targetUrl}projects/metadata/projectTemplates/${templateId}`; + + return axios({ + url: projectTemplateUrl, + headers, + }) +} diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index b3d963fd..d5f19ee9 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -17,6 +17,7 @@ import util from '../src/util'; const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName'); +const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType'); const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); @@ -293,6 +294,9 @@ function getRequestBody(indexName) { userId: { type: 'long', }, + projectId: { + type: 'long', + }, }, }, name: { @@ -623,8 +627,45 @@ function getRequestBody(indexName) { }, }, }, + + milestoneTemplates: { + type: 'nested', + properties: { + referenceId: { + type: 'long', + }, + reference: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + order: { + type: 'long', + }, + }, + }, }, }; + + const timelineMapping = { + _all: { enabled: false }, + properties: { + milestones: { + type: 'nested', + properties: { + id: { + type: 'long', + }, + timelineId: { + type: 'long', + }, + }, + }, + }, + }; + switch (indexName) { case ES_PROJECT_INDEX: result = { @@ -646,6 +687,16 @@ function getRequestBody(indexName) { }; result.body.mappings[ES_METADATA_TYPE] = metadataMapping; break; + case ES_TIMELINE_INDEX: + result = { + index: indexName, + updateAllTypes: true, + body: { + mappings: { }, + }, + }; + result.body.mappings[ES_TIMELINE_TYPE] = timelineMapping; + break; default: throw new Error(`Invalid index name '${indexName}'`); } @@ -661,7 +712,7 @@ esClient.indices.delete({ .then(() => esClient.indices.create(getRequestBody(ES_PROJECT_INDEX))) // Re-create timeline index .then(() => esClient.indices.delete({ index: ES_TIMELINE_INDEX, ignore: [404] })) -.then(() => esClient.indices.create({ index: ES_TIMELINE_INDEX })) +.then(() => esClient.indices.create(getRequestBody(ES_TIMELINE_INDEX))) // Re-create metadata index .then(() => esClient.indices.delete({ index: ES_METADATA_INDEX, ignore: [404] })) .then(() => esClient.indices.create(getRequestBody(ES_METADATA_INDEX))) diff --git a/package-lock.json b/package-lock.json index 7fbf46b4..bd4af3b9 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": { @@ -39,7 +39,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.24", "negotiator": "0.6.2" } }, @@ -55,7 +55,7 @@ "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "dev": true, "requires": { - "acorn": "^3.0.4" + "acorn": "3.3.0" }, "dependencies": { "acorn": { @@ -71,7 +71,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": { @@ -79,10 +79,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" } }, "ajv-keywords": { @@ -96,12 +96,12 @@ "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.3.tgz", "integrity": "sha512-ZOdUhMxcF+u62rPI+hMtU1NBXSDFQ3eCJJrenamtdQ7YYwh7RZJHOIM1gonVbZ5PyVdYH4xqBPje9OYqk7fnqw==", "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.5.5", + "buffer-more-ints": "1.0.0", + "readable-stream": "1.1.14", + "safe-buffer": "5.1.2", + "url-parse": "1.4.7" } }, "analytics-node": { @@ -109,15 +109,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.0", + "crypto-token": "1.0.1", + "debug": "2.6.9", + "lodash": "4.17.11", + "remove-trailing-slash": "0.1.0", + "superagent": "3.8.3", + "superagent-retry": "0.6.0" } }, "ansi-align": { @@ -126,7 +126,7 @@ "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "dev": true, "requires": { - "string-width": "^2.0.0" + "string-width": "2.1.1" }, "dependencies": { "ansi-regex": { @@ -147,8 +147,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": { @@ -157,7 +157,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -196,8 +196,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": { @@ -211,7 +211,7 @@ "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", "dev": true, "requires": { - "default-require-extensions": "^1.0.0" + "default-require-extensions": "1.0.0" } }, "argparse": { @@ -219,7 +219,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": { @@ -229,7 +229,7 @@ "dev": true, "optional": true, "requires": { - "arr-flatten": "^1.0.1" + "arr-flatten": "1.1.0" } }, "arr-flatten": { @@ -255,8 +255,8 @@ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "1.1.3", + "es-abstract": "1.13.0" } }, "array-unique": { @@ -271,7 +271,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": { @@ -308,8 +308,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.0", + "shimmer": "1.2.1" } }, "asynckit": { @@ -328,13 +328,13 @@ "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.10.4.tgz", "integrity": "sha512-FKwwA0E9NDrn/MxCzeftA5wx6YCos9wINhuvXeFHDdx+Lis7ykR46kBXJF4+dYjDdC5QhQ7W0cAp6bBS5gS75Q==", "requires": { - "base64-js": "^1.2.0", - "idtoken-verifier": "^1.2.0", - "js-cookie": "^2.2.0", - "qs": "^6.4.0", - "superagent": "^3.8.2", - "url-join": "^4.0.0", - "winchan": "^0.2.1" + "base64-js": "1.3.0", + "idtoken-verifier": "1.3.1", + "js-cookie": "2.2.0", + "qs": "6.7.0", + "superagent": "3.8.3", + "url-join": "4.0.0", + "winchan": "0.2.1" } }, "aws-sdk": { @@ -369,7 +369,7 @@ "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", "requires": { "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" + "is-buffer": "2.0.3" }, "dependencies": { "debug": { @@ -385,7 +385,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" } }, "is-buffer": { @@ -401,21 +401,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.0", + "convert-source-map": "1.6.0", + "fs-readdir-recursive": "1.1.0", + "glob": "7.1.4", + "lodash": "4.17.11", + "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": { @@ -424,8 +424,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } }, "glob": { @@ -434,12 +434,12 @@ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -450,9 +450,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.2", + "js-tokens": "3.0.2" } }, "babel-core": { @@ -461,25 +461,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.6.0", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.11", + "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": { @@ -488,8 +488,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } }, "json5": { @@ -506,10 +506,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": { @@ -518,14 +518,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.11", + "source-map": "0.5.7", + "trim-right": "1.0.1" }, "dependencies": { "babel-runtime": { @@ -534,8 +534,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -546,10 +546,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": { @@ -558,8 +558,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -570,10 +570,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.11" }, "dependencies": { "babel-runtime": { @@ -582,8 +582,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -594,11 +594,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": { @@ -607,8 +607,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -619,8 +619,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": { @@ -629,8 +629,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -641,8 +641,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": { @@ -651,8 +651,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -663,8 +663,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": { @@ -673,8 +673,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -685,9 +685,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.11" }, "dependencies": { "babel-runtime": { @@ -696,8 +696,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -708,12 +708,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": { @@ -722,8 +722,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -734,8 +734,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": { @@ -744,8 +744,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -756,7 +756,7 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -765,8 +765,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -783,7 +783,7 @@ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -792,8 +792,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -804,7 +804,7 @@ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -813,8 +813,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -825,7 +825,7 @@ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -834,8 +834,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -846,11 +846,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.11" }, "dependencies": { "babel-runtime": { @@ -859,8 +859,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -871,15 +871,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": { @@ -888,8 +888,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -900,8 +900,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": { @@ -910,8 +910,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -922,7 +922,7 @@ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -931,8 +931,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -943,8 +943,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": { @@ -953,8 +953,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -965,7 +965,7 @@ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -974,8 +974,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -986,9 +986,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": { @@ -997,8 +997,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1009,7 +1009,7 @@ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1018,8 +1018,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1030,9 +1030,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": { @@ -1041,8 +1041,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1053,10 +1053,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": { @@ -1065,8 +1065,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1077,9 +1077,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": { @@ -1088,8 +1088,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1100,9 +1100,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": { @@ -1111,8 +1111,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1123,8 +1123,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": { @@ -1133,8 +1133,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1145,12 +1145,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": { @@ -1159,8 +1159,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1171,8 +1171,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": { @@ -1181,8 +1181,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1193,7 +1193,7 @@ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1202,8 +1202,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1214,9 +1214,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": { @@ -1225,8 +1225,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1237,7 +1237,7 @@ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1246,8 +1246,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1258,7 +1258,7 @@ "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1267,8 +1267,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1279,9 +1279,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": { @@ -1290,8 +1290,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1302,7 +1302,7 @@ "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", "dev": true, "requires": { - "regenerator-transform": "^0.10.0" + "regenerator-transform": "0.10.1" } }, "babel-plugin-transform-runtime": { @@ -1311,7 +1311,7 @@ "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" }, "dependencies": { "babel-runtime": { @@ -1320,8 +1320,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1332,8 +1332,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": { @@ -1342,8 +1342,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1354,9 +1354,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.9", + "regenerator-runtime": "0.10.5" }, "dependencies": { "babel-runtime": { @@ -1365,8 +1365,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" }, "dependencies": { "regenerator-runtime": { @@ -1391,30 +1391,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": { @@ -1423,13 +1423,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.9", + "home-or-tmp": "2.0.0", + "lodash": "4.17.11", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18" }, "dependencies": { "babel-runtime": { @@ -1438,8 +1438,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1449,7 +1449,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.9" } }, "babel-template": { @@ -1458,11 +1458,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.11" }, "dependencies": { "babel-runtime": { @@ -1471,8 +1471,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1483,15 +1483,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.11" }, "dependencies": { "babel-runtime": { @@ -1500,8 +1500,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1512,10 +1512,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.2", + "lodash": "4.17.11", + "to-fast-properties": "1.0.3" }, "dependencies": { "babel-runtime": { @@ -1524,8 +1524,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -1541,7 +1541,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": { @@ -1555,13 +1555,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.1", + "pascalcase": "0.1.1" }, "dependencies": { "define-property": { @@ -1570,7 +1570,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -1579,7 +1579,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -1588,7 +1588,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -1597,9 +1597,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.2" } }, "isobject": { @@ -1626,7 +1626,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": { @@ -1634,9 +1634,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.11", + "long": "4.0.0", + "protocol-buffers-schema": "3.3.2" } }, "binary-extensions": { @@ -1658,9 +1658,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": { @@ -1674,15 +1674,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" } }, "boxen": { @@ -1691,13 +1691,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": { @@ -1712,7 +1712,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "camelcase": { @@ -1727,9 +1727,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": { @@ -1750,8 +1750,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": { @@ -1760,7 +1760,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "supports-color": { @@ -1769,7 +1769,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -1779,7 +1779,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" } }, @@ -1790,9 +1790,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": { @@ -1806,9 +1806,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.0", + "ieee754": "1.1.8", + "isarray": "1.0.0" }, "dependencies": { "isarray": { @@ -1849,10 +1849,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.7", + "moment": "2.24.0", + "mv": "2.1.1", + "safe-json-stringify": "1.2.0" } }, "bytes": { @@ -1866,15 +1866,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.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" }, "dependencies": { "isobject": { @@ -1891,7 +1891,7 @@ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "^0.2.0" + "callsites": "0.2.0" } }, "callsites": { @@ -1923,9 +1923,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": { @@ -1934,7 +1934,7 @@ "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, "requires": { - "check-error": "^1.0.2" + "check-error": "1.0.2" } }, "chalk": { @@ -1942,11 +1942,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": { @@ -1968,15 +1968,15 @@ "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", + "fsevents": "1.2.9", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1" } }, "ci-info": { @@ -1997,10 +1997,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": { @@ -2009,7 +2009,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "isobject": { @@ -2032,12 +2032,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.0", + "es5-ext": "0.10.50", + "es6-iterator": "2.0.3", + "memoizee": "0.4.14", + "timers-ext": "0.1.7" } }, "cli-cursor": { @@ -2046,7 +2046,7 @@ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "dev": true, "requires": { - "restore-cursor": "^1.0.1" + "restore-cursor": "1.0.1" } }, "cli-width": { @@ -2061,9 +2061,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" }, "dependencies": { "ansi-regex": { @@ -2084,8 +2084,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": { @@ -2094,7 +2094,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -2109,8 +2109,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": { @@ -2145,8 +2145,8 @@ "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": { @@ -2174,7 +2174,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": { @@ -2197,7 +2197,7 @@ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", "requires": { - "mime-db": ">= 1.40.0 < 2" + "mime-db": "1.40.0" } }, "compression": { @@ -2205,13 +2205,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.17", "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": { @@ -2232,10 +2232,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.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" }, "dependencies": { "isarray": { @@ -2250,13 +2250,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "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.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -2265,7 +2265,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -2275,7 +2275,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": { @@ -2284,8 +2284,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": { @@ -2294,12 +2294,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.1.15", + "make-dir": "1.3.0", + "unique-string": "1.0.0", + "write-file-atomic": "2.4.3", + "xdg-basedir": "3.0.0" } }, "connection-parse": { @@ -2331,8 +2331,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": { @@ -2341,7 +2341,7 @@ "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", "dev": true, "requires": { - "safe-buffer": "~5.1.1" + "safe-buffer": "5.1.2" } }, "cookie": { @@ -2380,8 +2380,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": { @@ -2390,7 +2390,7 @@ "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "dev": true, "requires": { - "capture-stack-trace": "^1.0.0" + "capture-stack-trace": "1.0.1" } }, "cross-spawn": { @@ -2399,11 +2399,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.7.0", + "shebang-command": "1.2.0", + "which": "1.3.1" } }, "crypto-js": { @@ -2428,7 +2428,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "^0.10.9" + "es5-ext": "0.10.50" } }, "dashdash": { @@ -2436,7 +2436,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": { @@ -2494,7 +2494,7 @@ "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", "dev": true, "requires": { - "strip-bom": "^2.0.0" + "strip-bom": "2.0.0" }, "dependencies": { "strip-bom": { @@ -2503,7 +2503,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } } } @@ -2514,7 +2514,7 @@ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "object-keys": "1.1.1" } }, "define-property": { @@ -2523,8 +2523,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": { @@ -2533,7 +2533,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -2542,7 +2542,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -2551,9 +2551,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.2" } }, "isobject": { @@ -2591,7 +2591,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "^2.0.0" + "repeating": "2.0.1" } }, "diff": { @@ -2606,7 +2606,7 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "^2.0.2" + "esutils": "2.0.2" } }, "dot-prop": { @@ -2615,7 +2615,7 @@ "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "dev": true, "requires": { - "is-obj": "^1.0.0" + "is-obj": "1.0.1" } }, "dottie": { @@ -2629,7 +2629,7 @@ "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", "optional": true, "requires": { - "nan": "^2.10.0" + "nan": "2.14.0" } }, "duplexer3": { @@ -2643,8 +2643,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" } }, "ecdsa-sig-formatter": { @@ -2652,7 +2652,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": { @@ -2661,10 +2661,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.0", + "lru-cache": "4.1.5", + "semver": "5.7.0", + "sigmund": "1.0.1" }, "dependencies": { "lru-cache": { @@ -2673,8 +2673,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" } } } @@ -2689,9 +2689,9 @@ "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-16.1.1.tgz", "integrity": "sha512-OF2fIjcTPfq/4Tj6k4/SZr2IIlfWlBBQoy/em225mfevYFW1abN3nyXKWldXGV+eWI6LWNqB8lb3hAP4d6Rh/Q==", "requires": { - "agentkeepalive": "^3.4.1", - "chalk": "^1.0.0", - "lodash": "^4.17.10" + "agentkeepalive": "3.5.2", + "chalk": "1.1.3", + "lodash": "4.17.11" } }, "emitter-listener": { @@ -2699,7 +2699,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": { @@ -2719,7 +2719,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } }, "error-ex": { @@ -2728,7 +2728,7 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "is-arrayish": "0.2.1" } }, "es-abstract": { @@ -2737,12 +2737,12 @@ "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", "dev": true, "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "es-to-primitive": "1.2.0", + "function-bind": "1.1.1", + "has": "1.0.3", + "is-callable": "1.1.4", + "is-regex": "1.0.4", + "object-keys": "1.1.1" } }, "es-to-primitive": { @@ -2751,9 +2751,9 @@ "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "1.1.4", + "is-date-object": "1.0.1", + "is-symbol": "1.0.2" } }, "es5-ext": { @@ -2762,9 +2762,9 @@ "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", "dev": true, "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "^1.0.0" + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" } }, "es6-iterator": { @@ -2773,9 +2773,9 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "d": "1.0.0", + "es5-ext": "0.10.50", + "es6-symbol": "3.1.1" } }, "es6-map": { @@ -2784,12 +2784,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.0", + "es5-ext": "0.10.50", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" } }, "es6-promise-polyfill": { @@ -2803,11 +2803,11 @@ "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", + "d": "1.0.0", + "es5-ext": "0.10.50", + "es6-iterator": "2.0.3", "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" + "event-emitter": "0.3.5" } }, "es6-symbol": { @@ -2816,8 +2816,8 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.0", + "es5-ext": "0.10.50" } }, "es6-weak-map": { @@ -2826,10 +2826,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.0", + "es5-ext": "0.10.50", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" } }, "escape-html": { @@ -2848,10 +2848,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.2.0" } }, "eslint": { @@ -2860,41 +2860,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.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "glob": "7.1.4", + "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.11", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "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": { @@ -2903,12 +2903,12 @@ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "user-home": { @@ -2917,7 +2917,7 @@ "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", "dev": true, "requires": { - "os-homedir": "^1.0.0" + "os-homedir": "1.0.2" } } } @@ -2928,7 +2928,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": { @@ -2937,8 +2937,8 @@ "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", "dev": true, "requires": { - "debug": "^2.6.9", - "resolve": "^1.5.0" + "debug": "2.6.9", + "resolve": "1.11.1" } }, "eslint-module-utils": { @@ -2947,8 +2947,8 @@ "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", "dev": true, "requires": { - "debug": "^2.6.8", - "pkg-dir": "^2.0.0" + "debug": "2.6.9", + "pkg-dir": "2.0.0" } }, "eslint-plugin-import": { @@ -2957,17 +2957,17 @@ "integrity": "sha512-qeVf/UwXFJbeyLbxuY8RgqDyEKCkqV7YC+E5S5uOjAp4tOc8zj01JP3ucoBM8JcEqd1qRasJSg6LLlisirfy0Q==", "dev": true, "requires": { - "array-includes": "^3.0.3", - "contains-path": "^0.1.0", - "debug": "^2.6.9", + "array-includes": "3.0.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.0", - "has": "^1.0.3", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "read-pkg-up": "^2.0.0", - "resolve": "^1.11.0" + "eslint-import-resolver-node": "0.3.2", + "eslint-module-utils": "2.4.0", + "has": "1.0.3", + "lodash": "4.17.11", + "minimatch": "3.0.4", + "read-pkg-up": "2.0.0", + "resolve": "1.11.1" }, "dependencies": { "doctrine": { @@ -3000,8 +3000,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": { @@ -3016,7 +3016,7 @@ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "4.2.0" } }, "esrecurse": { @@ -3025,7 +3025,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "4.2.0" } }, "estraverse": { @@ -3051,8 +3051,8 @@ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.0", + "es5-ext": "0.10.50" } }, "events": { @@ -3066,13 +3066,13 @@ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.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": "6.0.5", + "get-stream": "4.1.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": { @@ -3088,7 +3088,7 @@ "dev": true, "optional": true, "requires": { - "is-posix-bracket": "^0.1.0" + "is-posix-bracket": "0.1.1" } }, "expand-range": { @@ -3098,7 +3098,7 @@ "dev": true, "optional": true, "requires": { - "fill-range": "^2.1.0" + "fill-range": "2.2.4" } }, "express": { @@ -3106,36 +3106,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" } }, "express-list-routes": { @@ -3143,8 +3143,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.3.3", + "lodash": "3.10.1" }, "dependencies": { "lodash": { @@ -3159,7 +3159,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": { @@ -3176,7 +3176,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.11" } }, "extend": { @@ -3190,8 +3190,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": { @@ -3200,7 +3200,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -3212,7 +3212,7 @@ "dev": true, "optional": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "extsprintf": { @@ -3242,8 +3242,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": { @@ -3252,8 +3252,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": { @@ -3274,8 +3274,8 @@ "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", "dev": true, "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" + "glob": "7.1.4", + "minimatch": "3.0.4" }, "dependencies": { "glob": { @@ -3284,12 +3284,12 @@ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -3301,11 +3301,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": { @@ -3314,12 +3314,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": { @@ -3328,7 +3328,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } }, "flat": { @@ -3337,7 +3337,7 @@ "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "dev": true, "requires": { - "is-buffer": "~2.0.3" + "is-buffer": "2.0.3" }, "dependencies": { "is-buffer": { @@ -3354,10 +3354,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.1.15", + "rimraf": "2.6.3", + "write": "0.2.1" }, "dependencies": { "glob": { @@ -3366,12 +3366,12 @@ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "rimraf": { @@ -3380,7 +3380,7 @@ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.4" } } } @@ -3398,7 +3398,7 @@ "dev": true, "optional": true, "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } }, "forever-agent": { @@ -3411,9 +3411,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.24" } }, "formatio": { @@ -3422,7 +3422,7 @@ "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", "dev": true, "requires": { - "samsam": "~1.1" + "samsam": "1.1.2" } }, "formidable": { @@ -3441,7 +3441,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "^0.2.2" + "map-cache": "0.2.2" } }, "fresh": { @@ -3455,9 +3455,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.1.15", + "jsonfile": "4.0.0", + "universalify": "0.1.2" } }, "fs-readdir-recursive": { @@ -3478,8 +3478,8 @@ "dev": true, "optional": true, "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" + "nan": "2.14.0", + "node-pre-gyp": "0.12.0" }, "dependencies": { "abbrev": { @@ -3491,7 +3491,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3512,12 +3513,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": 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" @@ -3532,17 +3535,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3659,7 +3665,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3671,6 +3678,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3685,6 +3693,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3692,12 +3701,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3716,6 +3727,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3796,7 +3808,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3808,6 +3821,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3893,7 +3907,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3929,6 +3944,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3948,6 +3964,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3991,12 +4008,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -4012,7 +4031,7 @@ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "dev": true, "requires": { - "is-property": "^1.0.2" + "is-property": "1.0.2" } }, "generate-object-property": { @@ -4021,7 +4040,7 @@ "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", "dev": true, "requires": { - "is-property": "^1.0.0" + "is-property": "1.0.2" } }, "get-caller-file": { @@ -4036,7 +4055,7 @@ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { - "pump": "^3.0.0" + "pump": "3.0.0" } }, "get-value": { @@ -4050,7 +4069,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": { @@ -4059,11 +4078,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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-base": { @@ -4073,8 +4092,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": { @@ -4083,7 +4102,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "^2.0.0" + "is-glob": "2.0.1" } }, "global-dirs": { @@ -4092,7 +4111,7 @@ "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "dev": true, "requires": { - "ini": "^1.3.4" + "ini": "1.3.5" } }, "globals": { @@ -4107,17 +4126,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.1.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" }, "dependencies": { "get-stream": { @@ -4146,10 +4165,10 @@ "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "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.6.0" }, "dependencies": { "source-map": { @@ -4170,8 +4189,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.10.0", + "har-schema": "2.0.0" } }, "has": { @@ -4180,7 +4199,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "^1.1.1" + "function-bind": "1.1.1" } }, "has-ansi": { @@ -4188,7 +4207,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": { @@ -4209,9 +4228,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": { @@ -4228,8 +4247,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": { @@ -4238,7 +4257,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -4247,7 +4266,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -4258,7 +4277,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -4268,8 +4287,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": { @@ -4289,8 +4308,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": { @@ -4309,10 +4328,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" } }, @@ -4321,9 +4340,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": { @@ -4331,7 +4350,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": { @@ -4339,7 +4358,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": { @@ -4347,12 +4366,12 @@ "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.3.1.tgz", "integrity": "sha512-o0aplv9JqTuHz9jywi3fXXAHUWZ0nEWSjS1qBawLU74C+iqScORwBFXoac2zVoggE1hTaImikE8vALkZQu9I3Q==", "requires": { - "base64-js": "^1.2.0", - "crypto-js": "^3.1.9-1", - "es6-promise-polyfill": "^1.2.0", - "isomorphic-unfetch": "^3.0.0", - "jsbn": "^0.1.0", - "url-join": "^1.1.0" + "base64-js": "1.3.0", + "crypto-js": "3.1.9-1", + "es6-promise-polyfill": "1.2.0", + "isomorphic-unfetch": "3.0.0", + "jsbn": "0.1.1", + "url-join": "1.1.0" }, "dependencies": { "url-join": { @@ -4401,8 +4420,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": { @@ -4422,19 +4441,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.11", + "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": { @@ -4449,7 +4468,7 @@ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.4.0" } }, "invert-kv": { @@ -4469,7 +4488,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-arguments": { @@ -4490,7 +4509,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "1.13.1" } }, "is-bluebird": { @@ -4516,7 +4535,7 @@ "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { - "ci-info": "^1.5.0" + "ci-info": "1.6.0" } }, "is-data-descriptor": { @@ -4525,7 +4544,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-date-object": { @@ -4540,9 +4559,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": { @@ -4567,7 +4586,7 @@ "dev": true, "optional": true, "requires": { - "is-primitive": "^2.0.0" + "is-primitive": "2.0.0" } }, "is-extendable": { @@ -4588,7 +4607,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-fullwidth-code-point": { @@ -4597,7 +4616,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-generator-function": { @@ -4612,7 +4631,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "is-installed-globally": { @@ -4621,8 +4640,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": { @@ -4637,11 +4656,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.1" } }, "is-npm": { @@ -4657,7 +4676,7 @@ "dev": true, "optional": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-obj": { @@ -4672,7 +4691,7 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "1.0.2" } }, "is-plain-object": { @@ -4681,7 +4700,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -4730,7 +4749,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "^1.0.1" + "has": "1.0.3" } }, "is-resolvable": { @@ -4757,7 +4776,7 @@ "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", "dev": true, "requires": { - "has-symbols": "^1.0.0" + "has-symbols": "1.0.0" } }, "is-typedarray": { @@ -4817,8 +4836,8 @@ "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.0.0.tgz", "integrity": "sha512-V0tmJSYfkKokZ5mgl0cmfQMTb7MLHsBMngTkbLY0eXvKqiVRRoZP04Ly+KhKrJfKtzC9E6Pp15Jo+bwh7Vi2XQ==", "requires": { - "node-fetch": "^2.2.0", - "unfetch": "^4.0.0" + "node-fetch": "2.6.0", + "unfetch": "4.1.0" } }, "isstream": { @@ -4832,14 +4851,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": { @@ -4848,17 +4867,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.2", + "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": { @@ -4867,7 +4886,7 @@ "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", "dev": true, "requires": { - "lodash": "^4.17.11" + "lodash": "4.17.11" } } } @@ -4884,7 +4903,7 @@ "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", "dev": true, "requires": { - "append-transform": "^0.4.0" + "append-transform": "0.4.0" } }, "istanbul-lib-instrument": { @@ -4893,13 +4912,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.0" } }, "istanbul-lib-report": { @@ -4908,10 +4927,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": { @@ -4920,7 +4939,7 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "1.0.0" } } } @@ -4931,11 +4950,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.6.3", + "source-map": "0.5.7" }, "dependencies": { "debug": { @@ -4944,7 +4963,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "glob": { @@ -4953,12 +4972,12 @@ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "ms": { @@ -4973,7 +4992,7 @@ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.4" } } } @@ -4984,7 +5003,7 @@ "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", "dev": true, "requires": { - "handlebars": "^4.0.3" + "handlebars": "4.1.2" } }, "jmespath": { @@ -4997,10 +5016,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": { @@ -5014,11 +5033,11 @@ "integrity": "sha512-OMwf/tPDpE/BLlYKqZOhqWsd3/z2N3KOlyn1wsCRGFwViE8LOQTcDtathQvHvZc+q+zWmcNAbwKSC+iJoMaH2Q==", "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.4", + "mkdirp": "0.5.1", + "nopt": "4.0.1" }, "dependencies": { "glob": { @@ -5027,12 +5046,12 @@ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "nopt": { @@ -5041,8 +5060,8 @@ "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.0.9", + "osenv": "0.1.5" } } } @@ -5064,8 +5083,8 @@ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "1.0.10", + "esprima": "4.0.1" } }, "jsbn": { @@ -5095,7 +5114,7 @@ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "0.0.0" } }, "json-stringify-safe": { @@ -5108,7 +5127,7 @@ "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" } }, "jsonfile": { @@ -5117,7 +5136,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.1.15" } }, "jsonify": { @@ -5137,16 +5156,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.0" }, "dependencies": { "ms": { @@ -5174,7 +5193,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": { @@ -5182,12 +5201,12 @@ "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.5.1.tgz", "integrity": "sha512-FcH0mrrfS/5CDArzw7y4AIsNJ15SkAb8XTmfEnpUDmEsUf/4KfSQifff7hYEyrZuKnhisn4L9Pme4fb/ZoHKqQ==", "requires": { - "debug": "^2.6.9", - "jsonwebtoken": "^8.5.1", - "limiter": "^1.1.4", - "lru-memoizer": "^1.12.0", - "ms": "^2.1.1", - "request": "^2.88.0" + "debug": "2.6.9", + "jsonwebtoken": "8.5.1", + "limiter": "1.1.4", + "lru-memoizer": "1.12.0", + "ms": "2.1.2", + "request": "2.88.0" }, "dependencies": { "ms": { @@ -5202,8 +5221,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": { @@ -5212,7 +5231,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } }, "latest-version": { @@ -5221,7 +5240,7 @@ "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "dev": true, "requires": { - "package-json": "^4.0.0" + "package-json": "4.0.1" } }, "lazy-ass": { @@ -5236,7 +5255,7 @@ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "^2.0.0" + "invert-kv": "2.0.0" } }, "le_node": { @@ -5265,8 +5284,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "1.1.2", + "type-check": "0.3.2" } }, "libpq": { @@ -5275,7 +5294,7 @@ "integrity": "sha512-0TVzqkbAZZiM8JJy5sagRyXOkvU9zTBlgGX6YdzuWECobc5F81Tp6uuS+djMZrnB5YN4O/ff52hsvXYBRW2gdQ==", "requires": { "bindings": "1.2.1", - "nan": "^2.10.0" + "nan": "2.14.0" }, "dependencies": { "bindings": { @@ -5296,10 +5315,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.1.15", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" } }, "locate-path": { @@ -5308,8 +5327,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" } }, "lock": { @@ -5363,7 +5382,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "2.4.2" }, "dependencies": { "ansi-styles": { @@ -5372,7 +5391,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "chalk": { @@ -5381,9 +5400,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": { @@ -5398,7 +5417,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -5420,7 +5439,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": { @@ -5434,8 +5453,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": { @@ -5443,10 +5462,10 @@ "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-1.12.0.tgz", "integrity": "sha1-7+ZXBsyKnMZT+A8NWm6jitlQ41I=", "requires": { - "lock": "~0.1.2", - "lodash": "^4.17.4", - "lru-cache": "~4.0.0", - "very-fast-args": "^1.1.0" + "lock": "0.1.4", + "lodash": "4.17.11", + "lru-cache": "4.0.2", + "very-fast-args": "1.1.0" } }, "lru-queue": { @@ -5455,7 +5474,7 @@ "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", "dev": true, "requires": { - "es5-ext": "~0.10.2" + "es5-ext": "0.10.50" } }, "make-dir": { @@ -5464,7 +5483,7 @@ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { - "pify": "^3.0.0" + "pify": "3.0.0" }, "dependencies": { "pify": { @@ -5481,7 +5500,7 @@ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { - "p-defer": "^1.0.0" + "p-defer": "1.0.0" } }, "map-cache": { @@ -5496,7 +5515,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "^1.0.0" + "object-visit": "1.0.1" } }, "math-random": { @@ -5517,9 +5536,9 @@ "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" + "map-age-cleaner": "0.1.3", + "mimic-fn": "2.1.0", + "p-is-promise": "2.1.0" } }, "memoizee": { @@ -5528,14 +5547,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.0", + "es5-ext": "0.10.50", + "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": { @@ -5543,8 +5562,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": { @@ -5558,9 +5577,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": { @@ -5575,19 +5594,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": { @@ -5624,7 +5643,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": { @@ -5638,8 +5657,8 @@ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "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": { @@ -5648,7 +5667,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -5705,7 +5724,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.1" } }, "find-up": { @@ -5714,7 +5733,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "glob": { @@ -5723,12 +5742,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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "has-flag": { @@ -5743,8 +5762,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": { @@ -5759,7 +5778,7 @@ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -5768,7 +5787,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.2.0" } }, "p-try": { @@ -5783,7 +5802,7 @@ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -5798,7 +5817,7 @@ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.25.tgz", "integrity": "sha512-DgEaTyN/z0HFaVcVbSyVCUU6HeFdnNC3vE4c9cgu2dgMTvjBUBdBzWfasTBmAW45u5OIMeCJtU8yNjM22DHucw==", "requires": { - "moment": ">= 2.9.0" + "moment": "2.24.0" } }, "ms": { @@ -5823,9 +5842,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": { @@ -5839,17 +5858,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.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "arr-diff": { @@ -5906,7 +5925,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.11" } }, "nice-try": { @@ -5921,15 +5940,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.134", + "bin-protocol": "3.1.1", + "bluebird": "3.5.5", + "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" } }, "node-environment-flags": { @@ -5938,8 +5957,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.0.3", + "semver": "5.7.0" } }, "node-fetch": { @@ -5953,16 +5972,16 @@ "integrity": "sha512-/DXLzd/GhiaDXXbGId5BzxP1GlsqtMGM9zTmkWrgXtSqjKmGSbLicM/oAy4FR0YWm14jCHRwnR31AHS2dYFHrg==", "dev": true, "requires": { - "chokidar": "^2.1.5", - "debug": "^3.1.0", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.6", - "semver": "^5.5.0", - "supports-color": "^5.2.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.2", - "update-notifier": "^2.5.0" + "chokidar": "2.1.6", + "debug": "3.2.6", + "ignore-by-default": "1.0.1", + "minimatch": "3.0.4", + "pstree.remy": "1.1.7", + "semver": "5.7.0", + "supports-color": "5.5.0", + "touch": "3.1.0", + "undefsafe": "2.0.2", + "update-notifier": "2.5.0" }, "dependencies": { "anymatch": { @@ -5971,8 +5990,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": { @@ -5981,7 +6000,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } } } @@ -6004,16 +6023,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": { @@ -6022,7 +6041,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -6033,18 +6052,18 @@ "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", "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", + "fsevents": "1.2.9", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "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.1.2" } }, "debug": { @@ -6053,7 +6072,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" }, "dependencies": { "ms": { @@ -6070,13 +6089,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": { @@ -6094,7 +6113,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -6103,7 +6122,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -6112,7 +6131,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6121,7 +6140,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -6132,7 +6151,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6141,7 +6160,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -6152,9 +6171,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": { @@ -6171,14 +6190,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": { @@ -6187,7 +6206,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -6196,7 +6215,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -6207,10 +6226,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": { @@ -6219,7 +6238,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -6230,8 +6249,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": { @@ -6240,7 +6259,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -6257,7 +6276,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -6266,7 +6285,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -6275,9 +6294,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.2" } }, "is-extglob": { @@ -6292,7 +6311,7 @@ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-number": { @@ -6301,7 +6320,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -6310,7 +6329,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -6333,19 +6352,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.2", + "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": { @@ -6360,7 +6379,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -6371,7 +6390,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1" + "abbrev": "1.0.9" } }, "normalize-package-data": { @@ -6380,10 +6399,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.7.1", + "resolve": "1.11.1", + "semver": "5.7.0", + "validate-npm-package-license": "3.0.4" } }, "normalize-path": { @@ -6392,7 +6411,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } }, "npm-run-path": { @@ -6401,7 +6420,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "^2.0.0" + "path-key": "2.0.1" } }, "number-is-nan": { @@ -6426,9 +6445,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": { @@ -6437,7 +6456,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -6454,7 +6473,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "^3.0.0" + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -6471,10 +6490,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.0", + "object-keys": "1.1.1" } }, "object.entries": { @@ -6483,10 +6502,10 @@ "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "define-properties": "1.1.3", + "es-abstract": "1.13.0", + "function-bind": "1.1.1", + "has": "1.0.3" } }, "object.getownpropertydescriptors": { @@ -6495,8 +6514,8 @@ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "define-properties": "1.1.3", + "es-abstract": "1.13.0" } }, "object.omit": { @@ -6506,8 +6525,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": { @@ -6516,7 +6535,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" }, "dependencies": { "isobject": { @@ -6545,7 +6564,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "onetime": { @@ -6560,8 +6579,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "minimist": "0.0.10", + "wordwrap": "0.0.3" }, "dependencies": { "minimist": { @@ -6584,12 +6603,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" } }, "os-homedir": { @@ -6604,9 +6623,9 @@ "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "execa": "1.0.0", + "lcid": "2.0.0", + "mem": "4.3.0" } }, "os-tmpdir": { @@ -6621,8 +6640,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": { @@ -6631,9 +6650,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.1.15", + "mkdirp": "0.5.1", + "object-assign": "4.1.1" } }, "p-defer": { @@ -6660,7 +6679,7 @@ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "1.0.0" } }, "p-locate": { @@ -6669,7 +6688,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "1.3.0" } }, "p-try": { @@ -6684,10 +6703,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.0" } }, "packet-reader": { @@ -6702,10 +6721,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": { @@ -6714,7 +6733,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "^1.2.0" + "error-ex": "1.3.2" } }, "parseurl": { @@ -6774,7 +6793,7 @@ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "pify": "^2.0.0" + "pify": "2.3.0" } }, "performance-now": { @@ -6790,9 +6809,9 @@ "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "0.1.3", - "pg-pool": "^2.0.4", - "pg-types": "~2.0.0", - "pgpass": "1.x", + "pg-pool": "2.0.6", + "pg-types": "2.0.1", + "pgpass": "1.0.2", "semver": "4.3.2" }, "dependencies": { @@ -6818,8 +6837,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.8", + "pg-types": "1.13.0", "readable-stream": "1.0.31" }, "dependencies": { @@ -6829,10 +6848,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": { @@ -6864,10 +6883,10 @@ "integrity": "sha512-b7y6QM1VF5nOeX9ukMQ0h8a9z89mojrBHXfJeSug4mhL0YpxNBm83ot2TROyoAmX/ZOX3UbwVO4EbH7i1ZZNiw==", "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": { @@ -6875,7 +6894,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": { @@ -6890,7 +6909,7 @@ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "^2.1.0" + "find-up": "2.1.0" } }, "pluralize": { @@ -6925,7 +6944,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.1" } }, "precond": { @@ -6985,7 +7004,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" } }, @@ -7011,8 +7030,8 @@ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" } }, "punycode": { @@ -7042,9 +7061,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.2", + "math-random": "1.0.4" }, "dependencies": { "is-number": { @@ -7085,10 +7104,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": { @@ -7097,9 +7116,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": { @@ -7108,8 +7127,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": { @@ -7117,10 +7136,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.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "readdirp": { @@ -7129,9 +7148,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.1.15", + "micromatch": "3.1.10", + "readable-stream": "2.3.6" }, "dependencies": { "arr-diff": { @@ -7152,16 +7171,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": { @@ -7170,7 +7189,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -7181,13 +7200,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": { @@ -7196,7 +7215,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -7205,7 +7224,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -7214,7 +7233,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -7223,7 +7242,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -7234,7 +7253,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -7243,7 +7262,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -7254,9 +7273,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": { @@ -7273,14 +7292,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": { @@ -7289,7 +7308,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -7298,7 +7317,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -7309,10 +7328,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": { @@ -7321,7 +7340,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -7332,7 +7351,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -7341,7 +7360,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -7350,9 +7369,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.2" } }, "is-number": { @@ -7361,7 +7380,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -7370,7 +7389,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -7399,19 +7418,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.2", + "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": { @@ -7420,13 +7439,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "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.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -7435,7 +7454,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -7446,8 +7465,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" } }, @@ -7467,7 +7486,7 @@ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "resolve": "^1.1.6" + "resolve": "1.11.1" } }, "reconnect-core": { @@ -7475,7 +7494,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": { @@ -7496,9 +7515,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": { @@ -7507,8 +7526,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -7520,7 +7539,7 @@ "dev": true, "optional": true, "requires": { - "is-equal-shallow": "^0.1.3" + "is-equal-shallow": "0.1.3" } }, "regex-not": { @@ -7529,8 +7548,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": { @@ -7539,9 +7558,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": { @@ -7550,8 +7569,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": { @@ -7560,7 +7579,7 @@ "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "dev": true, "requires": { - "rc": "^1.0.1" + "rc": "1.2.8" } }, "regjsgen": { @@ -7575,7 +7594,7 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { - "jsesc": "~0.5.0" + "jsesc": "0.5.0" }, "dependencies": { "jsesc": { @@ -7615,7 +7634,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "^1.0.0" + "is-finite": "1.0.2" } }, "request": { @@ -7623,26 +7642,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.8.0", + "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.24", + "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": { "qs": { @@ -7670,8 +7689,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": { @@ -7685,7 +7704,7 @@ "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", "dev": true, "requires": { - "path-parse": "^1.0.6" + "path-parse": "1.0.6" } }, "resolve-from": { @@ -7706,8 +7725,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": { @@ -7721,7 +7740,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": { @@ -7730,7 +7749,7 @@ "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", "optional": true, "requires": { - "glob": "^6.0.1" + "glob": "6.0.4" } }, "run-async": { @@ -7739,7 +7758,7 @@ "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", "dev": true, "requires": { - "once": "^1.3.0" + "once": "1.4.0" } }, "rx-lite": { @@ -7765,7 +7784,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "~0.1.10" + "ret": "0.1.15" } }, "safer-buffer": { @@ -7800,7 +7819,7 @@ "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "dev": true, "requires": { - "semver": "^5.0.3" + "semver": "5.7.0" } }, "send": { @@ -7809,18 +7828,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": { @@ -7835,21 +7854,21 @@ "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.8.7.tgz", "integrity": "sha512-1rubZM8fAyCt5ipyS+3HJ3Jbmb8WesLdPJ3jIbTD+78EbuPZILFEA5fK0mliVRBx7oM7oPULeVX0lxSRXBV1jw==", "requires": { - "bluebird": "^3.5.0", - "cls-bluebird": "^2.1.0", - "debug": "^4.1.1", - "dottie": "^2.0.0", + "bluebird": "3.5.5", + "cls-bluebird": "2.1.0", + "debug": "4.1.1", + "dottie": "2.0.1", "inflection": "1.12.0", - "lodash": "^4.17.11", - "moment": "^2.24.0", - "moment-timezone": "^0.5.21", - "retry-as-promised": "^3.1.0", - "semver": "^5.6.0", - "sequelize-pool": "^1.0.2", - "toposort-class": "^1.0.1", - "uuid": "^3.2.1", - "validator": "^10.11.0", - "wkx": "^0.4.6" + "lodash": "4.17.11", + "moment": "2.24.0", + "moment-timezone": "0.5.25", + "retry-as-promised": "3.2.0", + "semver": "5.7.0", + "sequelize-pool": "1.0.2", + "toposort-class": "1.0.1", + "uuid": "3.3.2", + "validator": "10.11.0", + "wkx": "0.4.7" }, "dependencies": { "debug": { @@ -7857,7 +7876,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": { @@ -7873,14 +7892,14 @@ "integrity": "sha512-twVQ02alCpr2XvxNmpi32C48WZs6xHTH1OFTfTS5Meg3BVqOM8ghiZoml4FITFjlD8sAJSQjlAHTwqTbuolA6Q==", "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.5.5", + "cli-color": "1.4.0", + "fs-extra": "7.0.1", + "js-beautify": "1.10.0", + "lodash": "4.17.11", + "resolve": "1.11.1", + "umzug": "2.2.0", + "yargs": "13.2.2" } }, "sequelize-pool": { @@ -7888,7 +7907,7 @@ "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-1.0.2.tgz", "integrity": "sha512-VMKl/gCCdIvB1gFZ7p+oqLFEyZEz3oMMYjkKvfEC7GoO9bBcxmfOOU9RdkoltfXGgBZFigSChihRly2gKtsh2w==", "requires": { - "bluebird": "^3.5.3" + "bluebird": "3.5.5" } }, "serve-static": { @@ -7896,9 +7915,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" } }, @@ -7914,10 +7933,10 @@ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "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": { @@ -7926,7 +7945,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -7942,7 +7961,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "1.0.0" } }, "shebang-regex": { @@ -7957,9 +7976,9 @@ "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", "dev": true, "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" + "glob": "7.1.4", + "interpret": "1.2.0", + "rechoir": "0.6.2" }, "dependencies": { "glob": { @@ -7968,12 +7987,12 @@ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -8009,7 +8028,7 @@ "formatio": "1.1.1", "lolex": "1.3.2", "samsam": "1.1.2", - "util": ">=0.10.3 <1" + "util": "0.12.0" } }, "sinon-chai": { @@ -8030,7 +8049,7 @@ "integrity": "sha512-SoltvxayTifWOgOGD6CTh+djcp5TaOa/zdbaA38wEH1ahF2azmiLOh8CPt6ExHf0pAJAsA9OCHTS7zK24Ym4yA==", "dev": true, "requires": { - "nan": ">=2.12.1" + "nan": "2.14.0" } }, "slice-ansi": { @@ -8045,14 +8064,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.2", + "use": "3.1.1" }, "dependencies": { "define-property": { @@ -8061,7 +8080,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -8070,7 +8089,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -8081,9 +8100,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": { @@ -8092,7 +8111,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -8101,7 +8120,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -8110,7 +8129,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -8119,9 +8138,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.2" } }, "isobject": { @@ -8144,7 +8163,7 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "^3.2.0" + "kind-of": "3.2.2" } }, "source-map": { @@ -8159,11 +8178,11 @@ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { - "atob": "^2.1.1", - "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": { @@ -8172,7 +8191,7 @@ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "source-map": "^0.5.6" + "source-map": "0.5.7" } }, "source-map-url": { @@ -8187,8 +8206,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.4" } }, "spdx-exceptions": { @@ -8203,8 +8222,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.4" } }, "spdx-license-ids": { @@ -8218,7 +8237,7 @@ "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "requires": { - "through": "2" + "through": "2.3.8" } }, "split-string": { @@ -8227,7 +8246,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": { @@ -8240,15 +8259,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" } }, "static-extend": { @@ -8257,8 +8276,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": { @@ -8267,7 +8286,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -8283,9 +8302,9 @@ "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_decoder": { @@ -8298,7 +8317,7 @@ "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": { @@ -8324,16 +8343,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.3.3", + "formidable": "1.2.1", + "methods": "1.1.2", + "mime": "1.6.0", + "qs": "6.7.0", + "readable-stream": "2.3.6" }, "dependencies": { "debug": { @@ -8341,7 +8360,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": { @@ -8359,13 +8378,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "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.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -8373,7 +8392,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" } } } @@ -8389,8 +8408,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": { @@ -8408,7 +8427,7 @@ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.0.6.tgz", "integrity": "sha512-7YkBfVWRYjvnGITs7vygM8VNF91iUwX8ReHQaTD9I7q8Ky8SYXZ81gyFc+kovE34iSbCC1yW+n2oII3b3uSxXQ==", "requires": { - "swagger-ui-dist": "^3.18.1" + "swagger-ui-dist": "3.22.3" } }, "table": { @@ -8417,12 +8436,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.11", "slice-ansi": "0.0.4", - "string-width": "^2.0.0" + "string-width": "2.1.1" }, "dependencies": { "ajv": { @@ -8431,8 +8450,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": { @@ -8453,8 +8472,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": { @@ -8463,24 +8482,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.10.4", + "axios": "0.19.0", + "bunyan": "1.8.12", + "jsonwebtoken": "8.5.1", + "jwks-rsa": "1.5.1", + "le_node": "1.8.0", + "lodash": "4.17.11", + "millisecond": "0.1.2", + "request": "2.88.0" } }, "term-size": { @@ -8489,7 +8507,7 @@ "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "dev": true, "requires": { - "execa": "^0.7.0" + "execa": "0.7.0" }, "dependencies": { "cross-spawn": { @@ -8498,9 +8516,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" } }, "execa": { @@ -8509,13 +8527,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" } }, "get-stream": { @@ -8549,8 +8567,8 @@ "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", "dev": true, "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "es5-ext": "0.10.50", + "next-tick": "1.0.0" } }, "to-fast-properties": { @@ -8565,7 +8583,7 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "to-regex": { @@ -8574,10 +8592,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": { @@ -8586,8 +8604,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": { @@ -8596,7 +8614,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } } } @@ -8611,7 +8629,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": { @@ -8625,7 +8643,7 @@ "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", "dev": true, "requires": { - "nopt": "~1.0.10" + "nopt": "1.0.10" }, "dependencies": { "nopt": { @@ -8634,7 +8652,7 @@ "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, "requires": { - "abbrev": "1" + "abbrev": "1.0.9" } } } @@ -8644,8 +8662,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.1.32", + "punycode": "1.4.1" }, "dependencies": { "punycode": { @@ -8671,7 +8689,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": { @@ -8685,7 +8703,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "1.1.2" } }, "type-detect": { @@ -8700,7 +8718,7 @@ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "mime-types": "2.1.24" } }, "typedarray": { @@ -8716,8 +8734,8 @@ "dev": true, "optional": true, "requires": { - "commander": "~2.20.0", - "source-map": "~0.6.1" + "commander": "2.20.0", + "source-map": "0.6.1" }, "dependencies": { "source-map": { @@ -8735,8 +8753,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.5.5" }, "dependencies": { "babel-runtime": { @@ -8745,8 +8763,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.9", + "regenerator-runtime": "0.11.1" } } } @@ -8757,7 +8775,7 @@ "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", "dev": true, "requires": { - "debug": "^2.2.0" + "debug": "2.6.9" } }, "underscore": { @@ -8776,10 +8794,10 @@ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" }, "dependencies": { "extend-shallow": { @@ -8788,7 +8806,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "set-value": { @@ -8797,10 +8815,10 @@ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" } } } @@ -8811,7 +8829,7 @@ "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "dev": true, "requires": { - "crypto-random-string": "^1.0.0" + "crypto-random-string": "1.0.0" } }, "universalify": { @@ -8831,8 +8849,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": { @@ -8841,9 +8859,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": { @@ -8895,16 +8913,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": { @@ -8913,7 +8931,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "chalk": { @@ -8922,9 +8940,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": { @@ -8939,7 +8957,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -8949,7 +8967,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": { @@ -8984,8 +9002,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": { @@ -8994,7 +9012,7 @@ "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, "requires": { - "prepend-http": "^1.0.1" + "prepend-http": "1.0.4" } }, "urlencode": { @@ -9002,7 +9020,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": { @@ -9024,10 +9042,10 @@ "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" + "is-arguments": "1.0.4", + "is-generator-function": "1.0.7", + "object.entries": "1.1.0", + "safe-buffer": "5.1.2" } }, "util-deprecate": { @@ -9051,7 +9069,7 @@ "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { - "user-home": "^1.1.1" + "user-home": "1.1.1" } }, "validate-npm-package-license": { @@ -9060,8 +9078,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": { @@ -9079,9 +9097,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" } }, "very-fast-args": { @@ -9095,7 +9113,7 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "which-module": { @@ -9110,7 +9128,7 @@ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "requires": { - "string-width": "^1.0.2 || 2" + "string-width": "1.0.2" } }, "widest-line": { @@ -9119,7 +9137,7 @@ "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { - "string-width": "^2.1.1" + "string-width": "2.1.1" }, "dependencies": { "ansi-regex": { @@ -9140,8 +9158,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": { @@ -9150,7 +9168,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -9165,7 +9183,7 @@ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.7.tgz", "integrity": "sha512-pHf546L96TK8RradLt1cWaIffstgv/zXZ14CGz5KnBs1AxBX0wm+IDphjJw0qrEqRv8P9W9CdTt8Z1unMRZ19A==", "requires": { - "@types/node": "*" + "@types/node": "12.0.7" } }, "wordwrap": { @@ -9180,8 +9198,8 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "string-width": "1.0.2", + "strip-ansi": "3.0.1" } }, "wrappy": { @@ -9195,7 +9213,7 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "mkdirp": "0.5.1" } }, "write-file-atomic": { @@ -9204,9 +9222,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.1.15", + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" } }, "wrr-pool": { @@ -9214,7 +9232,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.11" } }, "xdg-basedir": { @@ -9228,8 +9246,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": { @@ -9258,8 +9276,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.4" }, "dependencies": { "glob": { @@ -9267,12 +9285,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "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.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -9283,17 +9301,17 @@ "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", "dev": true, "requires": { - "cliui": "^4.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", - "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.0.0" + "cliui": "4.1.0", + "find-up": "3.0.0", + "get-caller-file": "2.0.5", + "os-locale": "3.1.0", + "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.0.0" }, "dependencies": { "ansi-regex": { @@ -9308,7 +9326,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "is-fullwidth-code-point": { @@ -9323,8 +9341,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": { @@ -9333,7 +9351,7 @@ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -9342,7 +9360,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.2.0" } }, "p-try": { @@ -9357,9 +9375,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": { @@ -9368,7 +9386,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "4.1.0" } } } @@ -9379,8 +9397,8 @@ "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "5.3.1", + "decamelize": "1.2.0" } }, "yargs-unparser": { @@ -9389,9 +9407,9 @@ "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", "dev": true, "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.11", - "yargs": "^12.0.5" + "flat": "4.1.0", + "lodash": "4.17.11", + "yargs": "12.0.5" }, "dependencies": { "ansi-regex": { @@ -9406,7 +9424,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "get-caller-file": { @@ -9427,8 +9445,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": { @@ -9437,7 +9455,7 @@ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -9446,7 +9464,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.2.0" } }, "p-try": { @@ -9467,8 +9485,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": { @@ -9477,7 +9495,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "yargs": { @@ -9486,18 +9504,18 @@ "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "3.0.0", + "get-caller-file": "1.0.3", + "os-locale": "3.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "11.1.1" } }, "yargs-parser": { @@ -9506,8 +9524,8 @@ "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "5.3.1", + "decamelize": "1.2.0" } } } diff --git a/src/constants.js b/src/constants.js index a9ea439d..dd4d25f3 100644 --- a/src/constants.js +++ b/src/constants.js @@ -75,8 +75,13 @@ export const EVENT = { MILESTONE_UPDATED: 'milestone.updated', MILESTONE_REMOVED: 'milestone.removed', + MILESTONE_TEMPLATE_ADDED: 'milestone.template.added', + MILESTONE_TEMPLATE_UPDATED: 'milestone.template.updated', + MILESTONE_TEMPLATE_REMOVED: 'milestone.template.removed', + PROJECT_MEMBER_INVITE_CREATED: 'project.member.invite.created', PROJECT_MEMBER_INVITE_UPDATED: 'project.member.invite.updated', + PROJECT_MEMBER_INVITE_REMOVED: 'project.member.invite.deleted', // project metadata PROJECT_METADATA_CREATE: 'project.metadata.create', @@ -91,59 +96,42 @@ export const EVENT = { }; export const BUS_API_EVENT = { - PROJECT_CREATED: 'project.notification.create', // 'notifications.connect.project.created', - PROJECT_UPDATED: 'project.notification.update', // 'connect.action.project.updated', - PROJECT_DELETED: 'project.notification.delete', // 'project deleted' - PROJECT_SUBMITTED_FOR_REVIEW: 'project.notification.update', // 'notifications.connect.project.submittedForReview', - PROJECT_APPROVED: 'project.notification.update', // 'notifications.connect.project.approved', - PROJECT_PAUSED: 'project.notification.update', // 'notifications.connect.project.paused', - PROJECT_COMPLETED: 'project.notification.update', // 'notifications.connect.project.completed', - PROJECT_CANCELED: 'project.notification.update', // 'notifications.connect.project.canceled', - PROJECT_ACTIVE: 'project.notification.update', // 'notifications.connect.project.active', - - PROJECT_PHASE_TRANSITION_ACTIVE: 'notifications.connect.project.phase.transition.active', - PROJECT_PHASE_TRANSITION_COMPLETED: 'notifications.connect.project.phase.transition.completed', - PROJECT_PHASE_UPDATE_PAYMENT: 'notifications.connect.project.phase.update.payment', - PROJECT_PHASE_UPDATE_PROGRESS: 'notifications.connect.project.phase.update.progress', - PROJECT_PHASE_UPDATE_SCOPE: 'notifications.connect.project.phase.update.scope', - - MEMBER_JOINED: 'notifications.connect.project.member.joined', - MEMBER_LEFT: 'notifications.connect.project.member.left', - MEMBER_REMOVED: 'notifications.connect.project.member.removed', - MEMBER_ASSIGNED_AS_OWNER: 'notifications.connect.project.member.assignedAsOwner', - MEMBER_JOINED_COPILOT: 'notifications.connect.project.member.copilotJoined', - MEMBER_JOINED_MANAGER: 'notifications.connect.project.member.managerJoined', - - PROJECT_LINK_CREATED: 'notifications.connect.project.linkCreated', - PROJECT_FILE_UPLOADED: 'notifications.connect.project.fileUploaded', - PROJECT_SPECIFICATION_MODIFIED: 'connect.action.project.updated.spec', - PROJECT_PROGRESS_MODIFIED: 'connect.action.project.updated.progress', - PROJECT_FILES_UPDATED: 'connect.action.project.files.updated', - PROJECT_TEAM_UPDATED: 'connect.action.project.team.updated', + PROJECT_CREATED: 'project.notification.create', + PROJECT_UPDATED: 'project.notification.update', + PROJECT_DELETED: 'project.notification.delete', + + PROJECT_MEMBER_ADDED: 'project.notification.create', + PROJECT_MEMBER_REMOVED: 'project.notification.delete', + PROJECT_MEMBER_UPDATED: 'project.notification.update', + + PROJECT_ATTACHMENT_ADDED: 'project.notification.create', + PROJECT_ATTACHMENT_REMOVED: 'project.notification.delete', + PROJECT_ATTACHMENT_UPDATED: 'project.notification.update', // When phase is added/updated/deleted from the project, // When product is added/deleted from a phase // When product is updated on any field other than specification - PROJECT_PLAN_UPDATED: 'connect.action.project.plan.updated', + PROJECT_PHASE_CREATED: 'project.notification.create', + PROJECT_PHASE_UPDATED: 'project.notification.update', + PROJECT_PHASE_DELETED: 'project.notification.delete', - PROJECT_PLAN_READY: 'connect.action.project.plan.ready', + // phase product + PROJECT_PHASE_PRODUCT_ADDED: 'project.notification.create', + PROJECT_PHASE_PRODUCT_UPDATED: 'project.notification.update', + PROJECT_PHASE_PRODUCT_REMOVED: 'project.notification.delete', - // When milestone is added/deleted to/from the phase, - // When milestone is updated for duration/startDate/endDate/status - TIMELINE_ADJUSTED: 'connect.action.timeline.adjusted', + // timeline + TIMELINE_CREATED: 'project.notification.create', + TIMELINE_UPDATED: 'project.notification.update', + TIMELINE_DELETED: 'project.notification.delete', - // When specification of a product is modified - PROJECT_PRODUCT_SPECIFICATION_MODIFIED: 'connect.action.project.product.update.spec', + MILESTONE_ADDED: 'project.notification.create', + MILESTONE_REMOVED: 'project.notification.delete', + MILESTONE_UPDATED: 'project.notification.update', - MILESTONE_ADDED: 'connect.action.timeline.milestone.added', - MILESTONE_REMOVED: 'connect.action.timeline.milestone.removed', - MILESTONE_UPDATED: 'connect.action.timeline.milestone.updated', - // When milestone is marked as active - MILESTONE_TRANSITION_ACTIVE: 'connect.action.timeline.milestone.transition.active', - // When milestone is marked as completed - MILESTONE_TRANSITION_COMPLETED: 'connect.action.timeline.milestone.transition.completed', - // When milestone is waiting for customers's input - MILESTONE_WAITING_CUSTOMER: 'connect.action.timeline.milestone.waiting.customer', + MILESTONE_TEMPLATE_ADDED: 'project.notification.create', + MILESTONE_TEMPLATE_REMOVED: 'project.notification.delete', + MILESTONE_TEMPLATE_UPDATED: 'project.notification.update', // TC Message Service events TOPIC_CREATED: 'notifications.connect.project.topic.created', @@ -152,12 +140,9 @@ export const BUS_API_EVENT = { POST_UPDATED: 'notifications.connect.project.post.edited', // Project Member Invites - PROJECT_MEMBER_INVITE_CREATED: 'notifications.connect.project.member.invite.created', - PROJECT_MEMBER_INVITE_REQUESTED: 'notifications.connect.project.member.invite.requested', - PROJECT_MEMBER_INVITE_UPDATED: 'notifications.connect.project.member.invite.updated', - PROJECT_MEMBER_INVITE_APPROVED: 'notifications.connect.project.member.invite.approved', - PROJECT_MEMBER_INVITE_REJECTED: 'notifications.connect.project.member.invite.rejected', - PROJECT_MEMBER_EMAIL_INVITE_CREATED: 'connect.action.email.project.member.invite.created', + PROJECT_MEMBER_INVITE_CREATED: 'project.notification.create', + PROJECT_MEMBER_INVITE_UPDATED: 'project.notification.update', + PROJECT_MEMBER_INVITE_REMOVED: 'project.notification.delete', // metadata PROJECT_METADATA_CREATE: 'project.notification.create', @@ -197,6 +182,8 @@ export const RESOURCES = { PROJECT: 'project', PROJECT_TEMPLATE: 'project.template', PROJECT_TYPE: 'project.type', + PROJECT_MEMBER: 'project.member', + PROJECT_MEMBER_INVITE: 'project.member.invite', ORG_CONFIG: 'project.orgConfig', FORM_VERSION: 'project.form.version', FORM_REVISION: 'project.form.revision', @@ -206,4 +193,10 @@ export const RESOURCES = { PLAN_CONFIG_REVISION: 'project.planConfig.revision', PRODUCT_TEMPLATE: 'product.template', PRODUCT_CATEGORY: 'product.category', + PHASE: 'project.phase', + PHASE_PRODUCT: 'project.phase.product', + TIMELINE: 'timeline', + MILESTONE: 'milestone', + MILESTONE_TEMPLATE: 'milestone.template', + ATTACHMENT: 'project.attachment', }; diff --git a/src/events/busApi.js b/src/events/busApi.js index e07be462..1dac2122 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -1,22 +1,7 @@ import _ from 'lodash'; import config from 'config'; -import { EVENT, BUS_API_EVENT, PROJECT_STATUS, PROJECT_PHASE_STATUS, PROJECT_MEMBER_ROLE, MILESTONE_STATUS, - INVITE_STATUS } - from '../constants'; +import { EVENT, BUS_API_EVENT } from '../constants'; import { createEvent } from '../services/busApi'; -import models from '../models'; -import getTopcoderProjectMembers from '../util'; - -/** - * Builds the connect project attachment url for the given project and attachment ids. - * - * @param {string|number} projectId the project id - * @param {string|number} attachmentId the attachment id - * @returns {string} the connect project attachment url - */ -function connectProjectAttachmentUrl(projectId, attachmentId) { - return `${config.get('connectProjectsUrl')}${projectId}/attachments/${attachmentId}`; -} /** * Builds the connect project url for the given project id. @@ -94,645 +79,216 @@ module.exports = (app, logger) => { /** * PROJECT_MEMBER_ADDED */ - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, member }) => { + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_MEMBER_ADDED event'); - let eventType; - switch (member.role) { - case PROJECT_MEMBER_ROLE.MANAGER: - eventType = BUS_API_EVENT.MEMBER_JOINED_MANAGER; - break; - case PROJECT_MEMBER_ROLE.COPILOT: - eventType = BUS_API_EVENT.MEMBER_JOINED_COPILOT; - break; - default: - eventType = BUS_API_EVENT.MEMBER_JOINED; - break; - } - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - createEvent(eventType, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - userId: member.userId, - initiatorUserId: req.authUser.userId, - }, logger); - - createEvent(BUS_API_EVENT.PROJECT_TEAM_UPDATED, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.PROJECT_MEMBER_ADDED, resource, logger); }); /** * PROJECT_MEMBER_REMOVED */ - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, member }) => { + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_MEMBER_REMOVED event'); - let eventType; - if (member.userId === req.authUser.userId) { - eventType = BUS_API_EVENT.MEMBER_LEFT; - } else { - eventType = BUS_API_EVENT.MEMBER_REMOVED; - } - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - if (project) { - createEvent(eventType, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - userId: member.userId, - initiatorUserId: req.authUser.userId, - }, logger); - - createEvent(BUS_API_EVENT.PROJECT_TEAM_UPDATED, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, resource, logger); }); /** * PROJECT_MEMBER_UPDATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, ({ req, original, updated }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_MEMBER_UPDATED event'); - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - if (project) { - if (updated.isPrimary && !original.isPrimary) { - createEvent(BUS_API_EVENT.MEMBER_ASSIGNED_AS_OWNER, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - userId: updated.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - - createEvent(BUS_API_EVENT.PROJECT_TEAM_UPDATED, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, resource, logger); }); /** * PROJECT_ATTACHMENT_ADDED */ - app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, ({ req, attachment }) => { + app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_ATTACHMENT_ADDED event'); - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - createEvent(BUS_API_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); - - createEvent(BUS_API_EVENT.PROJECT_FILES_UPDATED, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, resource, logger); }); - /** * PROJECT_ATTACHMENT_UPDATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, ({ req }) => { + app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_ATTACHMENT_UPDATED event'); - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - createEvent(BUS_API_EVENT.PROJECT_FILES_UPDATED, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, resource, logger); }); /** * PROJECT_ATTACHMENT_REMOVED */ - app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, ({ req }) => { + app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_ATTACHMENT_REMOVED event'); - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - createEvent(BUS_API_EVENT.PROJECT_FILES_UPDATED, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, resource, logger); }); - /** - * If the project is in draft status and the phase is in reviewed status, and it's the - * only phase in the project with that status, then send the plan ready event. - * - * @param {object} req the req - * @param {object} project the project - * @param {object} phase the phase that was created/updated - * @returns {Promise} void - */ - async function sendPlanReadyEventIfNeeded(req, project, phase) { - if (project.status === PROJECT_STATUS.DRAFT && - phase.status === PROJECT_PHASE_STATUS.REVIEWED) { - await models.ProjectPhase.count({ - where: { projectId: project.id, status: PROJECT_PHASE_STATUS.REVIEWED }, - }).then(((count) => { - // only send the plan ready event when this is the only reviewed phase in the project - if (count === 1) { - createEvent(BUS_API_EVENT.PROJECT_PLAN_READY, { - projectId: project.id, - phaseId: phase.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - })); - } - } - /** * PROJECT_PHASE_ADDED */ - app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, ({ req, created }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_PHASE_ADDED event'); - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - allowedUsers: created.status === PROJECT_PHASE_STATUS.DRAFT ? - getTopcoderProjectMembers(project.members) : null, - }, logger); - return sendPlanReadyEventIfNeeded(req, project, created); - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.PROJECT_PHASE_CREATED, resource, logger); }); /** * PROJECT_PHASE_REMOVED */ - app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, ({ req, deleted }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_PHASE_REMOVED event'); - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - allowedUsers: deleted.status === PROJECT_PHASE_STATUS.DRAFT ? - getTopcoderProjectMembers(project.members) : null, - }, logger); - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.PROJECT_PHASE_DELETED, resource, logger); }); /** * PROJECT_PHASE_UPDATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, ({ req, original, updated }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_PHASE_UPDATED event'); - const projectId = _.parseInt(req.params.projectId); - const phaseId = _.parseInt(req.params.phaseId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - logger.debug(`Fetched project ${projectId} for the phase ${phaseId}`); - const eventsMap = {}; - [ - ['duration', BUS_API_EVENT.PROJECT_PLAN_UPDATED], - ['startDate', BUS_API_EVENT.PROJECT_PLAN_UPDATED], - ['spentBudget', BUS_API_EVENT.PROJECT_PHASE_UPDATE_PAYMENT], - ['progress', [BUS_API_EVENT.PROJECT_PHASE_UPDATE_PROGRESS, BUS_API_EVENT.PROJECT_PROGRESS_MODIFIED]], - ['details', BUS_API_EVENT.PROJECT_PHASE_UPDATE_SCOPE], - ['status', BUS_API_EVENT.PROJECT_PHASE_TRANSITION_ACTIVE, PROJECT_PHASE_STATUS.ACTIVE], - ['status', BUS_API_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED, PROJECT_PHASE_STATUS.COMPLETED], - // ideally we should validate the old value being 'DRAFT' but there is no other status from which - // we can move phase to REVIEWED status - ['status', BUS_API_EVENT.PROJECT_PLAN_UPDATED, PROJECT_PHASE_STATUS.REVIEWED], - // ideally we should validate the old value being 'REVIEWED' but there is no other status from which - // we can move phase to DRAFT status - ['status', BUS_API_EVENT.PROJECT_PLAN_UPDATED, PROJECT_PHASE_STATUS.DRAFT], - ].forEach(([key, events, sendIfNewEqual]) => { - // eslint-disable-next-line no-param-reassign - events = Array.isArray(events) ? events : [events]; - // eslint-disable-next-line no-param-reassign - events = _.filter(events, e => !eventsMap[e]); - - // send event(s) only if the target field's value was updated, or when an update matches a "sendIfNewEqual" value - if ((!sendIfNewEqual && !_.isEqual(original[key], updated[key])) || - (original[key] !== sendIfNewEqual && updated[key] === sendIfNewEqual)) { - events.forEach(event => createEvent(event, { - projectId, - phaseId, - projectUrl: connectProjectUrl(projectId), - originalPhase: original, - updatedPhase: updated, - projectName: project.name, - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - allowedUsers: updated.status === PROJECT_PHASE_STATUS.DRAFT ? - getTopcoderProjectMembers(project.members) : null, - }, logger)); - events.forEach((event) => { eventsMap[event] = true; }); - } - }); - - return sendPlanReadyEventIfNeeded(req, project, updated); - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.PROJECT_PHASE_UPDATED, resource, logger); }); /** - * PROJECT_PHASE_PRODUCT_UPDATED - */ - app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, ({ req, original, updated }) => { // eslint-disable-line no-unused-vars - logger.debug('receive PROJECT_PHASE_PRODUCT_UPDATED event'); + * MILESTONE_ADDED. + */ + app.on(EVENT.ROUTING_KEY.MILESTONE_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive MILESTONE_ADDED event'); - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - // Spec changes - if (!_.isEqual(original.details, updated.details)) { - logger.debug(`Spec changed for product id ${updated.id}`); - - createEvent(BUS_API_EVENT.PROJECT_PRODUCT_SPECIFICATION_MODIFIED, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - - const watchProperties = ['name', 'estimatedPrice', 'actualPrice', 'details']; - if (!_.isEqual(_.pick(original, watchProperties), - _.pick(updated, watchProperties))) { - createEvent(BUS_API_EVENT.PROJECT_PLAN_UPDATED, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - allowedUsers: updated.status === PROJECT_PHASE_STATUS.DRAFT ? - getTopcoderProjectMembers(project.members) : null, - }, logger); - } - }).catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.MILESTONE_ADDED, resource, logger); }); /** - * Send milestone notification if needed. - * @param {Object} req the request - * @param {Object} original the original milestone - * @param {Object} updated the updated milestone - * @param {Object} project the project - * @param {Object} timeline the updated timeline - * @returns {Promise} void - */ - function sendMilestoneNotification(req, original, updated, project, timeline) { - logger.debug('sendMilestoneNotification', original, updated); - // throw generic milestone updated bus api event - createEvent(BUS_API_EVENT.MILESTONE_UPDATED, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - timeline, - originalMilestone: original, - updatedMilestone: updated, - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - // Send transition events - if (original.status !== updated.status) { - let event; - if (updated.status === MILESTONE_STATUS.COMPLETED) { - event = BUS_API_EVENT.MILESTONE_TRANSITION_COMPLETED; - } else if (updated.status === MILESTONE_STATUS.ACTIVE) { - event = BUS_API_EVENT.MILESTONE_TRANSITION_ACTIVE; - } - - if (event) { - createEvent(event, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - timeline, - originalMilestone: original, - updatedMilestone: updated, - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - } - - // Send notifications.connect.project.phase.milestone.waiting.customer event - const originalWaiting = _.get(original, 'details.metadata.waitingForCustomer', false); - const updatedWaiting = _.get(updated, 'details.metadata.waitingForCustomer', false); - if (!originalWaiting && updatedWaiting) { - createEvent(BUS_API_EVENT.MILESTONE_WAITING_CUSTOMER, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - timeline, - originalMilestone: original, - updatedMilestone: updated, - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - } + * MILESTONE_UPDATED. + */ + app.on(EVENT.ROUTING_KEY.MILESTONE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug(`receive MILESTONE_UPDATED event for milestone ${resource.id}`); + + createEvent(BUS_API_EVENT.MILESTONE_UPDATED, resource, logger); + }); + + /** + * MILESTONE_REMOVED. + */ + app.on(EVENT.ROUTING_KEY.MILESTONE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive MILESTONE_REMOVED event'); + + createEvent(BUS_API_EVENT.MILESTONE_REMOVED, resource, logger); + }); /** - * MILESTONE_ADDED. + * MILESTONE_TEMPLATE_ADDED. */ - app.on(EVENT.ROUTING_KEY.MILESTONE_ADDED, ({ req, created }) => { + app.on(EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive MILESTONE_ADDED event'); - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - if (project) { - createEvent(BUS_API_EVENT.MILESTONE_ADDED, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - addedMilestone: created, - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - // sendMilestoneNotification(req, {}, created, project); - }) - .catch(err => null); // eslint-disable-line no-unused-vars + createEvent(BUS_API_EVENT.MILESTONE_TEMPLATE_ADDED, resource, logger); }); /** - * MILESTONE_UPDATED. + * MILESTONE_TEMPLATE_UPDATED. */ - // eslint-disable-next-line no-unused-vars - app.on(EVENT.ROUTING_KEY.MILESTONE_UPDATED, ({ req, original, updated, cascadedUpdates }) => { - logger.debug(`receive MILESTONE_UPDATED event for milestone ${original.id}`); - - const projectId = _.parseInt(req.params.projectId); - const timeline = _.omit(req.timeline.toJSON(), 'deletedAt', 'deletedBy'); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - logger.debug(`Found project with id ${projectId}`); - return models.Milestone.getTimelineDuration(timeline.id) - .then(({ duration, progress }) => { - timeline.duration = duration; - timeline.progress = progress; - sendMilestoneNotification(req, original, updated, project, timeline); - - logger.debug('cascadedUpdates', cascadedUpdates); - if (cascadedUpdates && cascadedUpdates.milestones && cascadedUpdates.milestones.length > 0) { - _.each(cascadedUpdates.milestones, cascadedUpdate => - sendMilestoneNotification(req, cascadedUpdate.original, cascadedUpdate.updated, project, timeline), - ); - } - - // if timeline is modified - if (cascadedUpdates && cascadedUpdates.timeline) { - const cTimeline = cascadedUpdates.timeline; - // if endDate of the timeline is modified, raise TIMELINE_ADJUSTED event - if (cTimeline.original.endDate !== cTimeline.updated.endDate) { - // Raise Timeline changed event - createEvent(BUS_API_EVENT.TIMELINE_ADJUSTED, { - projectId: project.id, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(project.id), - originalTimeline: cTimeline.original, - updatedTimeline: cTimeline.updated, - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - } - }); - }).catch(err => null); // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug(`receive MILESTONE_TEMPLATE_UPDATED event for milestone ${resource.id}`); + + createEvent(BUS_API_EVENT.MILESTONE_TEMPLATE_UPDATED, resource, logger); }); /** - * MILESTONE_REMOVED. + * MILESTONE_TEMPLATE_REMOVED. */ - app.on(EVENT.ROUTING_KEY.MILESTONE_REMOVED, ({ req, deleted }) => { - logger.debug('receive MILESTONE_REMOVED event'); - // req.params.projectId is set by validateTimelineIdParam middleware - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - if (project) { - createEvent(BUS_API_EVENT.MILESTONE_REMOVED, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - removedMilestone: deleted, - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - }).catch(err => null); // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive MILESTONE_TEMPLATE_REMOVED event'); + + createEvent(BUS_API_EVENT.MILESTONE_TEMPLATE_REMOVED, resource, logger); }); - app.on(EVENT.ROUTING_KEY.TIMELINE_UPDATED, ({ req, original, updated }) => { + /** + * TIMELINE_ADDED + */ + app.on(EVENT.ROUTING_KEY.TIMELINE_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive TIMELINE_ADDED event'); + + createEvent(BUS_API_EVENT.TIMELINE_CREATED, resource, logger); + }); + + /** + * TIMELINE_REMOVED + */ + app.on(EVENT.ROUTING_KEY.TIMELINE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive TIMELINE_REMOVED event'); + + createEvent(BUS_API_EVENT.TIMELINE_DELETED, resource, logger); + }); + + /** + * TIMELINE_UPDATED + */ + app.on(EVENT.ROUTING_KEY.TIMELINE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive TIMELINE_UPDATED event'); - // send PROJECT_UPDATED Kafka message when one of the specified below properties changed - const watchProperties = ['startDate', 'endDate']; - if (!_.isEqual(_.pick(original, watchProperties), - _.pick(updated, watchProperties))) { - // req.params.projectId is set by validateTimelineIdParam middleware - const projectId = _.parseInt(req.params.projectId); - - models.Project.findOne({ - where: { id: projectId }, - }) - .then((project) => { - if (project) { - createEvent(BUS_API_EVENT.TIMELINE_ADJUSTED, { - projectId, - projectName: project.name, - refCode: _.get(project, 'details.utm.code'), - projectUrl: connectProjectUrl(projectId), - originalTimeline: original, - updatedTimeline: updated, - userId: req.authUser.userId, - initiatorUserId: req.authUser.userId, - }, logger); - } - }).catch(err => null); // eslint-disable-line no-unused-vars - } + + createEvent(BUS_API_EVENT.TIMELINE_UPDATED, resource, logger); }); - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, ({ req, userId, email, status, role }) => { + /** + * PROJECT_PHASE_PRODUCT_ADDED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive PROJECT_PHASE_PRODUCT_ADDED event'); + + createEvent(BUS_API_EVENT.TIMELINE_CREATED, resource, logger); + }); + + /** + * PROJECT_PHASE_PRODUCT_REMOVED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive PROJECT_PHASE_PRODUCT_REMOVED event'); + + createEvent(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_REMOVED, resource, logger); + }); + + /** + * PROJECT_PHASE_PRODUCT_UPDATED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive PROJECT_PHASE_PRODUCT_UPDATED event'); + + createEvent(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, resource, logger); + }); + + /** + * PROJECT_MEMBER_INVITE_CREATED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_MEMBER_INVITE_CREATED event'); - const projectId = _.parseInt(req.params.projectId); - - if (status === INVITE_STATUS.REQUESTED) { - createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REQUESTED, { - projectId, - userId, - email, - role, - initiatorUserId: req.authUser.userId, - }, logger); - } else { - // send event to bus api - createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, { - projectId, - userId, - email, - role, - initiatorUserId: req.authUser.userId, - }, logger); - } + + createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, resource, logger); }); - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, ({ req, userId, email, status, role, createdBy }) => { + /** + * PROJECT_MEMBER_INVITE_UPDATED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_MEMBER_INVITE_UPDATED event'); - const projectId = _.parseInt(req.params.projectId); - - if (status === INVITE_STATUS.REQUEST_APPROVED) { - // send event to bus api - createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_APPROVED, { - projectId, - userId, - originator: createdBy, - email, - role, - status, - initiatorUserId: req.authUser.userId, - }, logger); - } else if (status === INVITE_STATUS.REQUEST_REJECTED) { - // send event to bus api - createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REJECTED, { - projectId, - userId, - originator: createdBy, - email, - role, - status, - initiatorUserId: req.authUser.userId, - }, logger); - } else { - // send event to bus api - createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, { - projectId, - userId, - email, - role, - status, - initiatorUserId: req.authUser.userId, - }, logger); - } + + createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, resource, logger); + }); + + /** + * PROJECT_MEMBER_INVITE_REMOVED + */ + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + logger.debug('receive PROJECT_MEMBER_INVITE_REMOVED event'); + + createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_REMOVED, resource, logger); }); }; diff --git a/src/events/index.js b/src/events/index.js index a4d5945f..67b1b151 100644 --- a/src/events/index.js +++ b/src/events/index.js @@ -59,7 +59,7 @@ export const rabbitHandlers = { }; export const kafkaHandlers = { - // Events defined by project-service + // Events defined by project-api [BUS_API_EVENT.PROJECT_UPDATED]: projectUpdatedKafkaHandler, [BUS_API_EVENT.PROJECT_FILES_UPDATED]: projectUpdatedKafkaHandler, [BUS_API_EVENT.PROJECT_TEAM_UPDATED]: projectUpdatedKafkaHandler, diff --git a/src/middlewares/fieldLookupValidation.js b/src/middlewares/fieldLookupValidation.js index d1fafb15..b811d6cf 100644 --- a/src/middlewares/fieldLookupValidation.js +++ b/src/middlewares/fieldLookupValidation.js @@ -2,8 +2,8 @@ import _ from 'lodash'; /** * Constructs a middleware the validates the existence of a record given a path to find in the req. For example, in - * order to check for a ProductCategory being received in "req.body.param.category" you would construct this middleware - * by calling this function with (models.ProductCategory, 'key', 'body.param.category', 'Category'). + * order to check for a ProductCategory being received in "req.body.category" you would construct this middleware + * by calling this function with (models.ProductCategory, 'key', 'body.category', 'Category'). * Note that this also works for updates where the value might not be present in the request, in which the case * the built middleware will continue without errors. * diff --git a/src/middlewares/validateMilestoneTemplate.js b/src/middlewares/validateMilestoneTemplate.js index 3ed55ab1..eee1fd7e 100644 --- a/src/middlewares/validateMilestoneTemplate.js +++ b/src/middlewares/validateMilestoneTemplate.js @@ -41,12 +41,12 @@ const validateMilestoneTemplate = { */ // eslint-disable-next-line valid-jsdoc validateRequestBody: (req, res, next) => { - validateReference(req.body.param, req) + validateReference(req.body, req) .then(() => { - if (req.body.param.sourceReference) { + if (req.body.sourceReference) { return validateReference({ - reference: req.body.param.sourceReference, - referenceId: req.body.param.sourceReferenceId, + reference: req.body.sourceReference, + referenceId: req.body.sourceReferenceId, }); } @@ -72,10 +72,7 @@ const validateMilestoneTemplate = { } // Validate the filter - const filter = util.parseQueryFilter(req.query.filter); - - // Save the parsed filter for later - req.params.filter = filter; + const filter = req.query.filter; if (!util.isValidFilter(filter, ['reference', 'referenceId'])) { const apiErr = new Error('Only allowed to filter by reference and referenceId'); diff --git a/src/middlewares/validateTimeline.js b/src/middlewares/validateTimeline.js index 6cebe413..4d10f4d7 100644 --- a/src/middlewares/validateTimeline.js +++ b/src/middlewares/validateTimeline.js @@ -83,7 +83,7 @@ const validateTimeline = { */ // eslint-disable-next-line valid-jsdoc validateTimelineRequestBody: (req, res, next) => { - validateReference(req.body.param, req, true) + validateReference(req.body, req, true) .then(next) .catch(next); }, @@ -100,10 +100,7 @@ const validateTimeline = { // eslint-disable-next-line valid-jsdoc validateTimelineQueryFilter: (req, res, next) => { // Validate the filter - const filter = util.parseQueryFilter(req.query.filter); - - // Save the parsed filter for later - req.params.filter = filter; + const filter = req.query; if (!util.isValidFilter(filter, ['reference', 'referenceId'])) { const apiErr = new Error('Only allowed to filter by reference and referenceId'); diff --git a/src/models/timeline.js b/src/models/timeline.js index 8ce0996a..d9cce184 100644 --- a/src/models/timeline.js +++ b/src/models/timeline.js @@ -3,6 +3,8 @@ /** * The Timeline model */ +import _ from 'lodash'; + module.exports = (sequelize, DataTypes) => { const Timeline = sequelize.define('Timeline', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, @@ -31,5 +33,36 @@ module.exports = (sequelize, DataTypes) => { Timeline.hasMany(models.Milestone, { as: 'milestones', foreignKey: 'timelineId', onDelete: 'cascade' }); }; + /** + * Search keyword in name, description, details.utm.code (To be deprecated) + * @param filters: the filters contains reference & referenceId + * @param log the request log + * @return the result rows + */ + Timeline.search = (filters, log) => { + // special handling for keyword filter + let query = '1=1 '; + const replacements = {}; + if (_.has(filters, 'reference')) { + query += 'AND timelines.reference = :reference '; + replacements.reference = filters.reference; + } + if (_.has(filters, 'referenceId')) { + query += 'AND timelines."referenceId" = :referenceId'; + replacements.referenceId = filters.referenceId; + } + + // select timelines + return sequelize.query(`SELECT * FROM timelines AS timelines + WHERE ${query}`, + { type: sequelize.QueryTypes.SELECT, + replacements, + logging: (str) => { log.debug(str); }, + raw: true, + }) + .then(timelines => timelines); + }; + + return Timeline; }; diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index 4c3bcaf9..c594713c 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -11,23 +11,21 @@ import path from 'path'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { EVENT } from '../../constants'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = tcMiddleware.permissions; const addAttachmentValidations = { - body: { - param: Joi.object().keys({ - title: Joi.string().required(), - 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(), - allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null), - }).required(), - }, + body: Joi.object().keys({ + title: Joi.string().required(), + 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(), + allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null), + }).required(), }; module.exports = [ @@ -39,7 +37,7 @@ module.exports = [ * In development mode we have to mock the ec2 file transfer and file service calls */ (req, res, next) => { - const data = req.body.param; + const data = req.body; // default values const projectId = req.params.projectId; const allowedUsers = data.allowedUsers; @@ -73,11 +71,9 @@ module.exports = [ // get pre-signed Url req.log.debug('requesting presigned Url'); httpClient.post(`${fileServiceUrl}uploadurl/`, { - param: { - filePath, - contentType: data.contentType, - isPublic: false, - }, + filePath, + contentType: data.contentType, + isPublic: false, }).then((resp) => { req.log.debug('Presigned Url resp: ', JSON.stringify(resp.data, null, 2)); if (resp.status !== 200 || resp.data.result.status !== 200) { @@ -117,9 +113,7 @@ module.exports = [ // retrieve download url for the response req.log.debug('retrieving download url'); return httpClient.post(`${fileServiceUrl}downloadurl`, { - param: { - filePath, - }, + filePath, }); } return Promise.resolve(); @@ -140,8 +134,14 @@ module.exports = [ newAttachment, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, { req, attachment: newAttachment }); - res.status(201).json(util.wrapResponse(req.id, response, 1, 201)); + + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, + RESOURCES.ATTACHMENT, + newAttachment); + res.status(201).json(response); accept(); } }); @@ -156,8 +156,14 @@ module.exports = [ newAttachment, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, { req, attachment: newAttachment }); - res.status(201).json(util.wrapResponse(req.id, response, 1, 201)); + // 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) => { diff --git a/src/routes/attachments/create.spec.js b/src/routes/attachments/create.spec.js index ef0d6650..0b150f6c 100644 --- a/src/routes/attachments/create.spec.js +++ b/src/routes/attachments/create.spec.js @@ -7,7 +7,7 @@ import models from '../../models'; import util from '../../util'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES } from '../../constants'; const should = chai.should(); @@ -107,7 +107,7 @@ describe('Project Attachments', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(403, done); }); @@ -118,14 +118,14 @@ describe('Project Attachments', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); postSpy.should.have.been.calledOnce; getSpy.should.have.been.calledOnce; @@ -150,13 +150,13 @@ describe('Project Attachments', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends single BUS_API_EVENT.PROJECT_FILES_UPDATED message when attachment added', (done) => { + it('sends BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED message when attachment added', (done) => { request(server) .post(`/v5/projects/${project1.id}/attachments/`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: body }) + .send(body) .expect(201) .end((err) => { if (err) { @@ -164,15 +164,17 @@ describe('Project Attachments', () => { } else { // Wait for app message handler to complete testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_FILE_UPLOADED); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_FILES_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; + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, + sinon.match({ resource: RESOURCES.ATTACHMENT })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, + sinon.match({ title: body.title })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, + sinon.match({ description: body.description })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, + sinon.match({ category: body.category })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, + sinon.match({ contentType: body.contentType })).should.be.true; done(); }); } diff --git a/src/routes/attachments/delete.js b/src/routes/attachments/delete.js index f5292b88..e96050f3 100644 --- a/src/routes/attachments/delete.js +++ b/src/routes/attachments/delete.js @@ -6,8 +6,9 @@ import { middleware as tcMiddleware, } from 'tc-core-library-js'; import models from '../../models'; +import util from '../../util'; import fileService from '../../services/fileService'; -import { EVENT } from '../../constants'; +import { EVENT, RESOURCES } from '../../constants'; /** * API to delete a project member. @@ -54,7 +55,13 @@ module.exports = [ pattachment, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, { req, pattachment }); + // req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, { req, pattachment }); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, + RESOURCES.ATTACHMENT, + { id: attachmentId }); res.status(204).json({}); }) .catch(err => next(err)); diff --git a/src/routes/attachments/delete.spec.js b/src/routes/attachments/delete.spec.js index 6f910152..59112545 100644 --- a/src/routes/attachments/delete.spec.js +++ b/src/routes/attachments/delete.spec.js @@ -9,8 +9,9 @@ import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES } from '../../constants'; +const should = chai.should(); // eslint-disable-line no-unused-vars describe('Project Attachments delete', () => { let project1; @@ -77,7 +78,7 @@ describe('Project Attachments delete', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: { userId: 1, projectId: project1.id, role: 'customer' } }) + .send({ userId: 1, projectId: project1.id, role: 'customer' }) .expect(403, done); }); @@ -87,7 +88,7 @@ describe('Project Attachments delete', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: { userId: 1, projectId: project1.id, role: 'customer' } }) + .send({ userId: 1, projectId: project1.id, role: 'customer' }) .expect(404, done); }); @@ -130,7 +131,7 @@ describe('Project Attachments delete', () => { if (!res) { throw new Error('Should found the entity'); } else { - deleteSpy.should.have.been.calledOnce; + deleteSpy.calledOnce.should.be.true; chai.assert.isNotNull(res.deletedAt); chai.assert.isNotNull(res.deletedBy); @@ -153,7 +154,7 @@ describe('Project Attachments delete', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: { userId: 1, projectId: project1.id, role: 'customer' } }) + .send({ userId: 1, projectId: project1.id, role: 'customer' }) .expect(204, done) .end((err) => { if (err) { @@ -181,7 +182,7 @@ describe('Project Attachments delete', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends single BUS_API_EVENT.PROJECT_FILES_UPDATED message when attachment deleted', (done) => { + it('sends BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED message when attachment deleted', (done) => { request(server) .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ @@ -195,13 +196,10 @@ describe('Project Attachments delete', () => { // Wait for app message handler to complete testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_FILES_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; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, + sinon.match({ resource: RESOURCES.ATTACHMENT })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, + sinon.match({ id: attachment.id })).should.be.true; done(); }); } diff --git a/src/routes/attachments/download.js b/src/routes/attachments/download.js index ddbe4595..e88b0b81 100644 --- a/src/routes/attachments/download.js +++ b/src/routes/attachments/download.js @@ -11,39 +11,74 @@ import util from '../../util'; const permissions = tcMiddleware.permissions; +const getFileDownloadUrl = (req, filePath) => { + if (process.env.NODE_ENV === 'development' || config.get('enableFileUpload') === false) { + return ['dummy://url']; + } + return util.getFileDownloadUrl(req, filePath); +}; + module.exports = [ permissions('project.downloadAttachment'), (req, res, next) => { const projectId = _.parseInt(req.params.projectId); const attachmentId = _.parseInt(req.params.id); - models.ProjectAttachment.findOne( - { - where: { - id: attachmentId, - projectId, + util.fetchByIdFromES('attachments', { + query: { + nested: { + path: 'attachments', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'attachments.id': attachmentId } }, + { term: { 'attachments.projectId': projectId } }, + ], + }, + }, + }, + }, + inner_hits: {}, }, - }) - .then((attachment) => { - if (!attachment) { - const err = new Error('Record not found'); - err.status = 404; - return Promise.reject(err); - } - if (process.env.NODE_ENV === 'development' || config.get('enableFileUpload') === false) { - return ['dummy://url']; + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No attachment found in ES'); + return models.ProjectAttachment.findOne( + { + where: { + id: attachmentId, + projectId, + }, + }) + .then((attachment) => { + if (!attachment) { + const err = new Error('Record not found'); + err.status = 404; + return Promise.reject(err); + } + + return getFileDownloadUrl(req, attachment.filePath); + }) + .catch((error) => { + req.log.error('Error fetching attachment', error); + const rerr = error; + rerr.status = rerr.status || 500; + next(rerr); + }); } - return util.getFileDownloadUrl(req, attachment.filePath); + 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); }) .then((result) => { const url = result[1]; - res.status(200).json(util.wrapResponse(req.id, { url })); + return res.json({ url }); }) - .catch((error) => { - req.log.error('Error fetching attachment', error); - const rerr = error; - rerr.status = rerr.status || 500; - next(rerr); - }); + .catch(next); }, ]; diff --git a/src/routes/attachments/update.js b/src/routes/attachments/update.js index c276f644..54cc74d2 100644 --- a/src/routes/attachments/update.js +++ b/src/routes/attachments/update.js @@ -7,7 +7,7 @@ import { } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { EVENT } from '../../constants'; +import { EVENT, RESOURCES } from '../../constants'; /** * API to update a project member. @@ -15,13 +15,11 @@ import { EVENT } from '../../constants'; const permissions = tcMiddleware.permissions; const updateProjectAttachmentValidation = { - body: { - param: Joi.object().keys({ - title: Joi.string().required(), - description: Joi.string().optional().allow(null).allow(''), - allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null), - }), - }, + body: Joi.object().keys({ + title: Joi.string().required(), + description: Joi.string().optional().allow(null).allow(''), + allowedUsers: Joi.array().items(Joi.number().integer().positive()).allow(null).default(null), + }), }; module.exports = [ @@ -32,7 +30,7 @@ module.exports = [ * Update a attachment if the user has access */ (req, res, next) => { - const updatedProps = req.body.param; + const updatedProps = req.body; const projectId = _.parseInt(req.params.projectId); const attachmentId = _.parseInt(req.params.id); let previousValue; @@ -56,14 +54,20 @@ module.exports = [ } })).then((updated) => { req.log.debug('updated project attachment', JSON.stringify(updated, null, 2)); - res.json(util.wrapResponse(req.id, updated)); + res.json(updated); // emit original and updated project information req.app.services.pubsub.publish( EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, { original: previousValue, updated: updated.get({ plain: true }) }, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, { req, original: previousValue, updated }); + + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, + RESOURCES.ATTACHMENT, + updated.toJSON()); }).catch(err => next(err))); }, ]; diff --git a/src/routes/attachments/update.spec.js b/src/routes/attachments/update.spec.js index 44982c59..4b7c774c 100644 --- a/src/routes/attachments/update.spec.js +++ b/src/routes/attachments/update.spec.js @@ -7,7 +7,7 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES } from '../../constants'; const should = chai.should(); @@ -77,7 +77,7 @@ describe('Project Attachments update', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: { title: 'updated title', description: 'updated description' } }) + .send({ title: 'updated title', description: 'updated description' }) .expect(403, done); }); @@ -87,7 +87,7 @@ describe('Project Attachments update', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: { title: 'updated title', description: 'updated description' } }) + .send({ title: 'updated title', description: 'updated description' }) .expect(404, done); }); @@ -97,13 +97,13 @@ describe('Project Attachments update', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: { title: 'updated title', description: 'updated description' } }) + .send({ title: 'updated title', description: 'updated description' }) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.title.should.equal('updated title'); resJson.description.should.equal('updated description'); @@ -118,13 +118,13 @@ describe('Project Attachments update', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: { title: 'updated title 1', description: 'updated description 1' } }) + .send({ title: 'updated title 1', description: 'updated description 1' }) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.title.should.equal('updated title 1'); resJson.description.should.equal('updated description 1'); @@ -151,7 +151,7 @@ describe('Project Attachments update', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: { title: 'updated title', description: 'updated description' } }) + .send({ title: 'updated title', description: 'updated description' }) .expect(200) .end((err) => { if (err) { @@ -160,13 +160,12 @@ describe('Project Attachments update', () => { // Wait for app message handler to complete testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_FILES_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; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, + sinon.match({ resource: RESOURCES.ATTACHMENT })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, + sinon.match({ title: 'updated title' })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, + sinon.match({ description: 'updated description' })).should.be.true; done(); }); } diff --git a/src/routes/form/revision/get.js b/src/routes/form/revision/get.js index 4fdb08a7..cc0ec5d5 100644 --- a/src/routes/form/revision/get.js +++ b/src/routes/form/revision/get.js @@ -42,7 +42,7 @@ module.exports = [ inner_hits: {}, }, }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No form found in ES'); diff --git a/src/routes/form/version/get.js b/src/routes/form/version/get.js index a2850043..0779b9f5 100644 --- a/src/routes/form/version/get.js +++ b/src/routes/form/version/get.js @@ -40,7 +40,7 @@ module.exports = [ }, }, sort: { 'forms.version': 'desc' }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No form found in ES'); diff --git a/src/routes/form/version/getVersion.js b/src/routes/form/version/getVersion.js index 1c5fb463..345f2044 100644 --- a/src/routes/form/version/getVersion.js +++ b/src/routes/form/version/getVersion.js @@ -40,7 +40,7 @@ module.exports = [ }, }, sort: { 'forms.revision': 'desc' }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No form found in ES'); diff --git a/src/routes/milestoneTemplates/clone.js b/src/routes/milestoneTemplates/clone.js index bfe95a46..d6c21f96 100644 --- a/src/routes/milestoneTemplates/clone.js +++ b/src/routes/milestoneTemplates/clone.js @@ -7,20 +7,18 @@ import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; import models from '../../models'; -import { MILESTONE_TEMPLATE_REFERENCES } from '../../constants'; +import { EVENT, RESOURCES, MILESTONE_TEMPLATE_REFERENCES } from '../../constants'; import validateMilestoneTemplate from '../../middlewares/validateMilestoneTemplate'; const permissions = tcMiddleware.permissions; const schema = { - body: { - param: Joi.object().keys({ - sourceReference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(), - sourceReferenceId: Joi.number().integer().positive().required(), - reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(), - referenceId: Joi.number().integer().positive().required(), - }).required(), - }, + body: Joi.object().keys({ + sourceReference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(), + sourceReferenceId: Joi.number().integer().positive().required(), + reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(), + referenceId: Joi.number().integer().positive().required(), + }).required(), }; module.exports = [ @@ -30,12 +28,12 @@ module.exports = [ (req, res, next) => { let result; - return models.sequelize.transaction(tx => + return models.sequelize.transaction(tx => // eslint-disable-line no-unused-vars // Find the product template models.MilestoneTemplate.findAll({ where: { - reference: req.body.param.sourceReference, - referenceId: req.body.param.sourceReferenceId, + reference: req.body.sourceReference, + referenceId: req.body.sourceReferenceId, }, attributes: { exclude: ['id', 'deletedAt', 'createdAt', 'updatedAt', 'deletedBy'] }, raw: true, @@ -43,18 +41,18 @@ module.exports = [ .then((milestoneTemplatesToClone) => { const newMilestoneTemplates = _.cloneDeep(milestoneTemplatesToClone); _.each(newMilestoneTemplates, (milestone) => { - milestone.reference = req.body.param.reference; // eslint-disable-line no-param-reassign - milestone.referenceId = req.body.param.referenceId; // eslint-disable-line no-param-reassign + milestone.reference = req.body.reference; // eslint-disable-line no-param-reassign + milestone.referenceId = req.body.referenceId; // eslint-disable-line no-param-reassign milestone.createdBy = req.authUser.userId; // eslint-disable-line no-param-reassign milestone.updatedBy = req.authUser.userId; // eslint-disable-line no-param-reassign }); - return models.MilestoneTemplate.bulkCreate(newMilestoneTemplates, { transaction: tx }); + return models.MilestoneTemplate.bulkCreate(newMilestoneTemplates); }) .then(() => { // eslint-disable-line arrow-body-style return models.MilestoneTemplate.findAll({ where: { - reference: req.body.param.reference, - referenceId: req.body.param.referenceId, + reference: req.body.reference, + referenceId: req.body.referenceId, }, attributes: { exclude: ['deletedAt', 'deletedBy'] }, raw: true, @@ -66,8 +64,15 @@ module.exports = [ }), ) .then(() => { + // emit the event + _.map(result, r => util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_ADDED, + RESOURCES.MILESTONE_TEMPLATE, + r)); + // Write to response - res.status(201).json(util.wrapResponse(req.id, result, result.length, 201)); + res.status(201).json(result); }) .catch(next); }, diff --git a/src/routes/milestoneTemplates/clone.spec.js b/src/routes/milestoneTemplates/clone.spec.js index 7fde9b3f..4f233354 100644 --- a/src/routes/milestoneTemplates/clone.spec.js +++ b/src/routes/milestoneTemplates/clone.spec.js @@ -125,12 +125,10 @@ describe('CLONE milestone template', () => { describe('POST /timelines/metadata/milestoneTemplates/clone', () => { const body = { - param: { - sourceReference: 'productTemplate', - sourceReferenceId: 1, - reference: 'productTemplate', - referenceId: 2, - }, + sourceReference: 'productTemplate', + sourceReferenceId: 1, + reference: 'productTemplate', + referenceId: 2, }; it('should return 403 if user is not authenticated/clone', (done) => { @@ -172,12 +170,10 @@ describe('CLONE milestone template', () => { it('should return 400 for non-existent product template', (done) => { const invalidBody = { - param: { - sourceReference: 'productTemplate', - sourceReferenceId: 1, - reference: 'productTemplate', - referenceId: 2000, - }, + sourceReference: 'productTemplate', + sourceReferenceId: 1, + reference: 'productTemplate', + referenceId: 2000, }; request(server) @@ -191,12 +187,10 @@ describe('CLONE milestone template', () => { it('should return 400 for non-existent source product template', (done) => { const invalidBody = { - param: { - sourceReference: 'product', - sourceReferenceId: 1000, - reference: 'product', - referenceId: 2, - }, + sourceReference: 'product', + sourceReferenceId: 1000, + reference: 'product', + referenceId: 2, }; request(server) @@ -210,11 +204,9 @@ describe('CLONE milestone template', () => { it('should return 400 if missing sourceReference', (done) => { const invalidBody = { - param: { - sourceReferenceId: 1000, - reference: 'productTemplate', - referenceId: 2, - }, + sourceReferenceId: 1000, + reference: 'productTemplate', + referenceId: 2, }; request(server) @@ -236,7 +228,7 @@ describe('CLONE milestone template', () => { .send(body) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); should.not.equal(resJson[0].id, null); resJson[0].name.should.be.eql(milestoneTemplates[0].name); @@ -271,7 +263,7 @@ describe('CLONE milestone template', () => { .send(body) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].createdBy.should.be.eql(40051336); // connect admin resJson[0].updatedBy.should.be.eql(40051336); // connect admin diff --git a/src/routes/milestoneTemplates/create.js b/src/routes/milestoneTemplates/create.js index ad9018cb..e2fc5957 100644 --- a/src/routes/milestoneTemplates/create.js +++ b/src/routes/milestoneTemplates/create.js @@ -9,35 +9,33 @@ import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; import models from '../../models'; import validateMilestoneTemplate from '../../middlewares/validateMilestoneTemplate'; -import { MILESTONE_TEMPLATE_REFERENCES } from '../../constants'; +import { EVENT, RESOURCES, MILESTONE_TEMPLATE_REFERENCES } from '../../constants'; const permissions = tcMiddleware.permissions; const schema = { - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - name: Joi.string().max(255).required(), - description: Joi.string().max(255), - duration: Joi.number().integer().required(), - type: Joi.string().max(45).required(), - order: Joi.number().integer().required(), - plannedText: Joi.string().max(512).required(), - activeText: Joi.string().max(512).required(), - completedText: Joi.string().max(512).required(), - blockedText: Joi.string().max(512).required(), - reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(), - referenceId: Joi.number().integer().positive().required(), - metadata: Joi.object().required(), - hidden: Joi.boolean().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + id: Joi.any().strip(), + name: Joi.string().max(255).required(), + description: Joi.string().max(255), + duration: Joi.number().integer().required(), + type: Joi.string().max(45).required(), + order: Joi.number().integer().required(), + plannedText: Joi.string().max(512).required(), + activeText: Joi.string().max(512).required(), + completedText: Joi.string().max(512).required(), + blockedText: Joi.string().max(512).required(), + reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(), + referenceId: Joi.number().integer().positive().required(), + metadata: Joi.object().required(), + hidden: Joi.boolean().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -45,7 +43,7 @@ module.exports = [ validateMilestoneTemplate.validateRequestBody, permissions('milestoneTemplate.create'), (req, res, next) => { - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { createdBy: req.authUser.userId, updatedBy: req.authUser.userId, }); @@ -68,11 +66,42 @@ module.exports = [ }, transaction: tx, }); + }) + .then((updatedCount) => { + if (updatedCount) { + return models.MilestoneTemplate.findAll({ + where: { + reference: result.reference, + referenceId: result.referenceId, + id: { $ne: result.id }, + }, + order: [['updatedAt', 'DESC']], + limit: updatedCount[0], + transaction: tx, + }); + } + return Promise.resolve(); }), ) - .then(() => { + .then((otherUpdated) => { + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_ADDED, + RESOURCES.MILESTONE_TEMPLATE, + result); + + // emit the event for other milestone templates order updated + _.map(otherUpdated, milestoneTemplate => + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED, + RESOURCES.MILESTONE_TEMPLATE, + _.assign(_.pick(milestoneTemplate.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt'))), + ); + // Write to response - res.status(201).json(util.wrapResponse(req.id, result, 1, 201)); + res.status(201).json(result); }) .catch(next); }, diff --git a/src/routes/milestoneTemplates/create.spec.js b/src/routes/milestoneTemplates/create.spec.js index 9483ad11..0b1bb3cb 100644 --- a/src/routes/milestoneTemplates/create.spec.js +++ b/src/routes/milestoneTemplates/create.spec.js @@ -106,21 +106,19 @@ describe('CREATE milestone template', () => { describe('POST /timelines/metadata/milestoneTemplates', () => { const body = { - param: { - name: 'milestoneTemplate 3', - description: 'description 3', - duration: 33, - type: 'type3', - order: 1, - plannedText: 'text to be shown in planned stage - 3', - blockedText: 'text to be shown in blocked stage - 3', - activeText: 'text to be shown in active stage - 3', - completedText: 'text to be shown in completed stage - 3', - hidden: true, - reference: 'productTemplate', - referenceId: 1, - metadata: {}, - }, + name: 'milestoneTemplate 3', + description: 'description 3', + duration: 33, + type: 'type3', + order: 1, + plannedText: 'text to be shown in planned stage - 3', + blockedText: 'text to be shown in blocked stage - 3', + activeText: 'text to be shown in active stage - 3', + completedText: 'text to be shown in completed stage - 3', + hidden: true, + reference: 'productTemplate', + referenceId: 1, + metadata: {}, }; it('should return 403 if user is not authenticated', (done) => { @@ -161,9 +159,7 @@ describe('CREATE milestone template', () => { }); it('should return 400 for non-existed product template', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { referenceId: 1000 }), - }; + const invalidBody = _.assign({}, body, { referenceId: 1000 }); request(server) .post('/v5/timelines/metadata/milestoneTemplates') @@ -176,9 +172,7 @@ describe('CREATE milestone template', () => { it('should return 400 if missing name', (done) => { const invalidBody = { - param: { - name: undefined, - }, + name: undefined, }; request(server) @@ -193,9 +187,7 @@ describe('CREATE milestone template', () => { it('should return 400 if missing duration', (done) => { const invalidBody = { - param: { - duration: undefined, - }, + duration: undefined, }; request(server) @@ -210,9 +202,7 @@ describe('CREATE milestone template', () => { it('should return 400 if missing type', (done) => { const invalidBody = { - param: { - type: undefined, - }, + type: undefined, }; request(server) @@ -227,9 +217,7 @@ describe('CREATE milestone template', () => { it('should return 400 if missing order', (done) => { const invalidBody = { - param: { - order: undefined, - }, + order: undefined, }; request(server) @@ -252,20 +240,20 @@ describe('CREATE milestone template', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(body.param.name); - resJson.description.should.be.eql(body.param.description); - resJson.duration.should.be.eql(body.param.duration); - resJson.type.should.be.eql(body.param.type); - resJson.order.should.be.eql(body.param.order); - resJson.plannedText.should.be.eql(body.param.plannedText); - resJson.blockedText.should.be.eql(body.param.blockedText); - resJson.activeText.should.be.eql(body.param.activeText); - resJson.completedText.should.be.eql(body.param.completedText); - resJson.reference.should.be.eql(body.param.reference); - resJson.referenceId.should.be.eql(body.param.referenceId); - resJson.metadata.should.be.eql(body.param.metadata); + resJson.name.should.be.eql(body.name); + resJson.description.should.be.eql(body.description); + resJson.duration.should.be.eql(body.duration); + resJson.type.should.be.eql(body.type); + resJson.order.should.be.eql(body.order); + resJson.plannedText.should.be.eql(body.plannedText); + resJson.blockedText.should.be.eql(body.blockedText); + resJson.activeText.should.be.eql(body.activeText); + resJson.completedText.should.be.eql(body.completedText); + resJson.reference.should.be.eql(body.reference); + resJson.referenceId.should.be.eql(body.referenceId); + resJson.metadata.should.be.eql(body.metadata); resJson.createdBy.should.be.eql(40051333); // admin should.exist(resJson.createdAt); @@ -277,8 +265,8 @@ describe('CREATE milestone template', () => { // Verify 'order' of the other milestones models.MilestoneTemplate.findAll({ where: { - reference: body.param.reference, - referenceId: body.param.referenceId, + reference: body.reference, + referenceId: body.referenceId, }, }).then((milestones) => { _.each(milestones, (milestone) => { @@ -297,7 +285,7 @@ describe('CREATE milestone template', () => { it('should return 201 for admin without optional fields', (done) => { const minimalBody = _.cloneDeep(body); - delete minimalBody.param.hidden; + delete minimalBody.hidden; request(server) .post('/v5/timelines/metadata/milestoneTemplates') .set({ @@ -307,7 +295,7 @@ describe('CREATE milestone template', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.hidden.should.be.eql(false); // default of hidden field done(); }); @@ -323,7 +311,7 @@ describe('CREATE milestone template', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051336); // connect admin resJson.updatedBy.should.be.eql(40051336); // connect admin done(); diff --git a/src/routes/milestoneTemplates/delete.js b/src/routes/milestoneTemplates/delete.js index 5cd0454b..0ab2f7e0 100644 --- a/src/routes/milestoneTemplates/delete.js +++ b/src/routes/milestoneTemplates/delete.js @@ -5,6 +5,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; +import util from '../../util'; +import { EVENT, RESOURCES } from '../../constants'; import validateMilestoneTemplate from '../../middlewares/validateMilestoneTemplate'; const permissions = tcMiddleware.permissions; @@ -25,6 +27,13 @@ module.exports = [ .then(entity => entity.destroy()), ) .then(() => { + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_REMOVED, + RESOURCES.MILESTONE_TEMPLATE, + { id: req.params.milestoneTemplateId }); + res.status(204).end(); }) .catch(next), diff --git a/src/routes/milestoneTemplates/get.js b/src/routes/milestoneTemplates/get.js index 1f6cd2f7..2efdb3d3 100644 --- a/src/routes/milestoneTemplates/get.js +++ b/src/routes/milestoneTemplates/get.js @@ -20,5 +20,25 @@ module.exports = [ validate(schema), validateMilestoneTemplate.validateIdParam, permissions('milestoneTemplate.view'), - (req, res) => res.json(util.wrapResponse(req.id, _.omit(req.milestoneTemplate.toJSON(), 'deletedAt', 'deletedBy'))), + (req, res, next) => + util.fetchByIdFromES('milestoneTemplates', { + query: { + nested: { + path: 'milestoneTemplates', + query: { + match: { 'milestoneTemplates.id': req.params.milestoneTemplateId }, + }, + inner_hits: {}, + }, + }, + }, 'metadata') + .then((data) => { + if (data.length === 0) { + req.log.debug('No milestoneTemplate found in ES'); + return res.json(_.omit(req.milestoneTemplate.toJSON(), 'deletedAt', 'deletedBy')); + } + req.log.debug('milestoneTemplate found in ES'); + return res.json(data[0].inner_hits.milestoneTemplates.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + }) + .catch(next), ]; diff --git a/src/routes/milestoneTemplates/get.spec.js b/src/routes/milestoneTemplates/get.spec.js index 242e0960..42aada94 100644 --- a/src/routes/milestoneTemplates/get.spec.js +++ b/src/routes/milestoneTemplates/get.spec.js @@ -140,7 +140,7 @@ describe('GET milestone template', () => { }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(milestoneTemplates[0].id); resJson.name.should.be.eql(milestoneTemplates[0].name); resJson.duration.should.be.eql(milestoneTemplates[0].duration); diff --git a/src/routes/milestoneTemplates/list.js b/src/routes/milestoneTemplates/list.js index 1fc737c5..68528ef1 100644 --- a/src/routes/milestoneTemplates/list.js +++ b/src/routes/milestoneTemplates/list.js @@ -14,7 +14,7 @@ module.exports = [ permissions('milestoneTemplate.view'), (req, res, next) => { // Parse the sort query - let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'order'; + let sort = req.query.sort ? req.query.sort : 'order'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } @@ -29,16 +29,73 @@ module.exports = [ const sortColumnAndOrder = sort.split(' '); // Get all milestone templates - const where = req.params.filter || {}; - return models.MilestoneTemplate.findAll({ - where, - order: [sortColumnAndOrder], - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, + let where = {}; + let esTerms = []; + if (req.query.reference) { + where = _.assign(where, { + reference: req.query.reference, + }); + esTerms = _.concat(esTerms, { + term: { 'milestoneTemplates.reference': req.query.reference }, + }); + } + + if (req.query.referenceId) { + where = _.assign(where, { + referenceId: req.query.referenceId, + }); + esTerms = _.concat(esTerms, { + term: { 'milestoneTemplates.referenceId': req.query.referenceId }, + }); + } + + let query = {}; + if (esTerms.length > 0) { + query = { + query: { + nested: { + path: 'milestoneTemplates', + query: + { + filtered: { + filter: { + bool: { + must: esTerms, + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }; + } + + return util.fetchFromES('milestoneTemplates', query) + .then((data) => { + let milestoneTemplates = _.isArray(data.milestoneTemplates) ? + data.milestoneTemplates : data.milestoneTemplates.hits.hits; + if (milestoneTemplates.length === 0) { + req.log.debug('No milestoneTemplate found in ES'); + return models.MilestoneTemplate.findAll({ + where, + order: [sortColumnAndOrder], + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then(result => res.json(result)) + .catch(next); + } + req.log.debug('milestoneTemplates found in ES'); + // Get the milestoneTemplates + milestoneTemplates = _.map(milestoneTemplates, (m) => { + if (m._source) return m._source; // eslint-disable-line no-underscore-dangle + return m; + }); + // Sort + milestoneTemplates = _.orderBy(milestoneTemplates, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]); + return res.json(milestoneTemplates); }) - .then((milestoneTemplates) => { - res.json(util.wrapResponse(req.id, milestoneTemplates)); - }) - .catch(next); + .catch(next); }, ]; diff --git a/src/routes/milestoneTemplates/list.spec.js b/src/routes/milestoneTemplates/list.spec.js index 0fc02db3..74f668ac 100644 --- a/src/routes/milestoneTemplates/list.spec.js +++ b/src/routes/milestoneTemplates/list.spec.js @@ -141,7 +141,7 @@ describe('LIST milestone template', () => { it('should return 400 for invalid sort order', (done) => { request(server) - .get('/v5/timelines/metadata/milestoneTemplates?sort=order%20invalid') + .get('/v5/timelines/metadata/milestoneTemplates?sort=order invalid') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -156,7 +156,7 @@ describe('LIST milestone template', () => { }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].id.should.be.eql(milestoneTemplates[0].id); resJson[0].name.should.be.eql(milestoneTemplates[0].name); @@ -182,9 +182,9 @@ describe('LIST milestone template', () => { }); }); - it('should return 200 for connect admin with filter', (done) => { + it('should return 200 for connect admin with reference and referenceId filters', (done) => { request(server) - .get('/v5/timelines/metadata/milestoneTemplates?filter=reference%3DproductTemplate%26referenceId%3D1') + .get('/v5/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId=1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -228,7 +228,7 @@ describe('LIST milestone template', () => { }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].id.should.be.eql(2); resJson[1].id.should.be.eql(1); diff --git a/src/routes/milestoneTemplates/update.js b/src/routes/milestoneTemplates/update.js index c9ce67e4..3b1ce253 100644 --- a/src/routes/milestoneTemplates/update.js +++ b/src/routes/milestoneTemplates/update.js @@ -9,7 +9,7 @@ import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; import models from '../../models'; import validateMilestoneTemplate from '../../middlewares/validateMilestoneTemplate'; -import { MILESTONE_TEMPLATE_REFERENCES } from '../../constants'; +import { EVENT, RESOURCES, MILESTONE_TEMPLATE_REFERENCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -17,31 +17,29 @@ const schema = { params: { milestoneTemplateId: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - name: Joi.string().max(255).optional(), - description: Joi.string().max(255), - duration: Joi.number().integer().optional(), - type: Joi.string().max(45).optional(), - order: Joi.number().integer().optional(), - plannedText: Joi.string().max(512).optional(), - activeText: Joi.string().max(512).optional(), - completedText: Joi.string().max(512).optional(), - blockedText: Joi.string().max(512).optional(), - productTemplateId: Joi.any().strip(), - hidden: Joi.boolean().optional(), - reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(), - referenceId: Joi.number().integer().positive().required(), - metadata: Joi.object().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + id: Joi.any().strip(), + name: Joi.string().max(255).optional(), + description: Joi.string().max(255), + duration: Joi.number().integer().optional(), + type: Joi.string().max(45).optional(), + order: Joi.number().integer().optional(), + plannedText: Joi.string().max(512).optional(), + activeText: Joi.string().max(512).optional(), + completedText: Joi.string().max(512).optional(), + blockedText: Joi.string().max(512).optional(), + productTemplateId: Joi.any().strip(), + hidden: Joi.boolean().optional(), + reference: Joi.string().valid(_.values(MILESTONE_TEMPLATE_REFERENCES)).required(), + referenceId: Joi.number().integer().positive().required(), + metadata: Joi.object().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -50,7 +48,7 @@ module.exports = [ validateMilestoneTemplate.validateRequestBody, permissions('milestoneTemplate.edit'), (req, res, next) => { - const entityToUpdate = _.assign(req.body.param, { + const entityToUpdate = _.assign(req.body, { updatedBy: req.authUser.userId, }); @@ -108,10 +106,40 @@ module.exports = [ }, }); }); + }) + .then((updatedCount) => { + if (updatedCount) { + return models.MilestoneTemplate.findAll({ + where: { + reference: updated.reference, + referenceId: updated.referenceId, + id: { $ne: updated.id }, + }, + order: [['updatedAt', 'DESC']], + limit: updatedCount[0], + }); + } + return Promise.resolve(); }), ) - .then(() => { - res.json(util.wrapResponse(req.id, updated)); + .then((otherUpdated) => { + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED, + RESOURCES.MILESTONE_TEMPLATE, + _.assign(entityToUpdate, _.pick(updated, 'id', 'updatedAt'))); + + // emit the event for other milestone templates order updated + _.map(otherUpdated, milestoneTemplate => + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED, + RESOURCES.MILESTONE_TEMPLATE, + _.assign(_.pick(milestoneTemplate.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt'))), + ); + + res.json(updated); return Promise.resolve(); }) .catch(next); diff --git a/src/routes/milestoneTemplates/update.spec.js b/src/routes/milestoneTemplates/update.spec.js index 6eb462c1..ef76a799 100644 --- a/src/routes/milestoneTemplates/update.spec.js +++ b/src/routes/milestoneTemplates/update.spec.js @@ -173,21 +173,19 @@ describe('UPDATE milestone template', () => { describe('PATCH /timelines/metadata/milestoneTemplates/{milestoneTemplateId}', () => { const body = { - param: { - name: 'milestoneTemplate 1-updated', - description: 'description-updated', - duration: 6, - type: 'type1-updated', - order: 5, - plannedText: 'text to be shown in planned stage', - blockedText: 'text to be shown in blocked stage', - activeText: 'text to be shown in active stage', - completedText: 'text to be shown in completed stage', - hidden: true, - reference: 'productTemplate', - referenceId: 1, - metadata: {}, - }, + name: 'milestoneTemplate 1-updated', + description: 'description-updated', + duration: 6, + type: 'type1-updated', + order: 5, + plannedText: 'text to be shown in planned stage', + blockedText: 'text to be shown in blocked stage', + activeText: 'text to be shown in active stage', + completedText: 'text to be shown in completed stage', + hidden: true, + reference: 'productTemplate', + referenceId: 1, + metadata: {}, }; it('should return 403 if user is not authenticated', (done) => { @@ -256,20 +254,20 @@ describe('UPDATE milestone template', () => { .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(1); - resJson.name.should.be.eql(body.param.name); - resJson.description.should.be.eql(body.param.description); - resJson.duration.should.be.eql(body.param.duration); - resJson.type.should.be.eql(body.param.type); - resJson.order.should.be.eql(body.param.order); - resJson.plannedText.should.be.eql(body.param.plannedText); - resJson.blockedText.should.be.eql(body.param.blockedText); - resJson.activeText.should.be.eql(body.param.activeText); - resJson.completedText.should.be.eql(body.param.completedText); - resJson.reference.should.be.eql(body.param.reference); - resJson.referenceId.should.be.eql(body.param.referenceId); - resJson.metadata.should.be.eql(body.param.metadata); + resJson.name.should.be.eql(body.name); + resJson.description.should.be.eql(body.description); + resJson.duration.should.be.eql(body.duration); + resJson.type.should.be.eql(body.type); + resJson.order.should.be.eql(body.order); + resJson.plannedText.should.be.eql(body.plannedText); + resJson.blockedText.should.be.eql(body.blockedText); + resJson.activeText.should.be.eql(body.activeText); + resJson.completedText.should.be.eql(body.completedText); + resJson.reference.should.be.eql(body.reference); + resJson.referenceId.should.be.eql(body.referenceId); + resJson.metadata.should.be.eql(body.metadata); should.exist(resJson.createdBy); should.exist(resJson.createdAt); @@ -291,7 +289,7 @@ describe('UPDATE milestone template', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 3 }) }) // 1 to 3 + .send(_.assign({}, body, { order: 3 })) // 1 to 3 .expect(200) .end(() => { // Milestone 1: order 3 @@ -323,7 +321,7 @@ describe('UPDATE milestone template', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 4 }) }) // 1 to 4 + .send(_.assign({}, body, { order: 4 })) // 1 to 4 .expect(200) .end(() => { // Milestone 1: order 4 @@ -355,7 +353,7 @@ describe('UPDATE milestone template', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 1 }) }) // 3 to 1 + .send(_.assign({}, body, { order: 1 })) // 3 to 1 .expect(200) .end(() => { // Milestone 1: order 2 @@ -387,7 +385,7 @@ describe('UPDATE milestone template', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 0 }) }) // 3 to 0 + .send(_.assign({}, body, { order: 0 })) // 3 to 0 .expect(200) .end(() => { // Milestone 1: order 1 @@ -412,7 +410,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for missing name', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.name; + delete partialBody.name; request(server) .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ @@ -424,7 +422,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for missing type', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.type; + delete partialBody.type; request(server) .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ @@ -436,7 +434,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for missing duration', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.duration; + delete partialBody.duration; request(server) .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ @@ -448,7 +446,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for missing order', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.order; + delete partialBody.order; request(server) .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ @@ -460,7 +458,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for missing plannedText', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.plannedText; + delete partialBody.plannedText; request(server) .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ @@ -472,7 +470,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for missing blockedText', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.blockedText; + delete partialBody.blockedText; request(server) .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ @@ -484,7 +482,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for missing activeText', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.activeText; + delete partialBody.activeText; request(server) .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ @@ -496,7 +494,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for missing completedText', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.completedText; + delete partialBody.completedText; request(server) .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ @@ -508,7 +506,7 @@ describe('UPDATE milestone template', () => { it('should return 200 for missing hidden field', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.hidden; + delete partialBody.hidden; request(server) .patch('/v5/timelines/metadata/milestoneTemplates/1') .set({ @@ -531,35 +529,33 @@ describe('UPDATE milestone template', () => { it('should return 200 for admin - updating metadata', (done) => { const bodyWithMetadata = { - param: { - name: 'milestoneTemplate 5-updated', - description: 'description-updated', - duration: 6, - type: 'type5-updated', - order: 5, - plannedText: 'text to be shown in planned stage', - blockedText: 'text to be shown in blocked stage', - activeText: 'text to be shown in active stage', - completedText: 'text to be shown in completed stage', - hidden: true, - reference: 'productTemplate', - referenceId: 1, - metadata: { - metadata1: { - name: 'metadata 1 - update', - details: { - anyDetails: 'any details 1 - update', - newDetails: 'new', - }, - others: ['others new'], + name: 'milestoneTemplate 5-updated', + description: 'description-updated', + duration: 6, + type: 'type5-updated', + order: 5, + plannedText: 'text to be shown in planned stage', + blockedText: 'text to be shown in blocked stage', + activeText: 'text to be shown in active stage', + completedText: 'text to be shown in completed stage', + hidden: true, + reference: 'productTemplate', + referenceId: 1, + metadata: { + metadata1: { + name: 'metadata 1 - update', + details: { + anyDetails: 'any details 1 - update', + newDetails: 'new', }, - metadata3: { - name: 'metadata 3', - details: { - anyDetails: 'any details 3', - }, - others: ['others 31', 'others 32'], + others: ['others new'], + }, + metadata3: { + name: 'metadata 3', + details: { + anyDetails: 'any details 3', }, + others: ['others 31', 'others 32'], }, }, }; @@ -572,7 +568,7 @@ describe('UPDATE milestone template', () => { .send(bodyWithMetadata) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.metadata.should.be.eql({ metadata1: { name: 'metadata 1 - update', diff --git a/src/routes/milestones/create.js b/src/routes/milestones/create.js index b4409b8e..12848879 100644 --- a/src/routes/milestones/create.js +++ b/src/routes/milestones/create.js @@ -9,7 +9,7 @@ import Sequelize from 'sequelize'; import util from '../../util'; import validateTimeline from '../../middlewares/validateTimeline'; import models from '../../models'; -import { EVENT } from '../../constants'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -17,33 +17,31 @@ const schema = { params: { timelineId: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - name: Joi.string().max(255).required(), - description: Joi.string().max(255), - duration: Joi.number().integer().required(), - startDate: Joi.date().required(), - actualStartDate: Joi.date().allow(null), - endDate: Joi.date().min(Joi.ref('startDate')).allow(null), - completionDate: Joi.date().min(Joi.ref('startDate')).allow(null), - status: Joi.string().max(45).required(), - type: Joi.string().max(45).required(), - details: Joi.object(), - order: Joi.number().integer().required(), - plannedText: Joi.string().max(512).required(), - activeText: Joi.string().max(512).required(), - completedText: Joi.string().max(512).required(), - blockedText: Joi.string().max(512).required(), - hidden: Joi.boolean().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + id: Joi.any().strip(), + name: Joi.string().max(255).required(), + description: Joi.string().max(255), + duration: Joi.number().integer().required(), + startDate: Joi.date().required(), + actualStartDate: Joi.date().allow(null), + endDate: Joi.date().min(Joi.ref('startDate')).allow(null), + completionDate: Joi.date().min(Joi.ref('startDate')).allow(null), + status: Joi.string().max(45).required(), + type: Joi.string().max(45).required(), + details: Joi.object(), + order: Joi.number().integer().required(), + plannedText: Joi.string().max(512).required(), + activeText: Joi.string().max(512).required(), + completedText: Joi.string().max(512).required(), + blockedText: Joi.string().max(512).required(), + hidden: Joi.boolean().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -53,7 +51,7 @@ module.exports = [ validateTimeline.validateTimelineIdParam, permissions('milestone.create'), (req, res, next) => { - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { createdBy: req.authUser.userId, updatedBy: req.authUser.userId, timelineId: req.params.timelineId, @@ -62,9 +60,9 @@ module.exports = [ // Validate startDate and endDate to be within the timeline startDate and endDate let error; - if (req.body.param.startDate < req.timeline.startDate) { + if (req.body.startDate < req.timeline.startDate) { error = 'Milestone startDate must not be before the timeline startDate'; - } else if (req.body.param.endDate && req.timeline.endDate && req.body.param.endDate > req.timeline.endDate) { + } else if (req.body.endDate && req.timeline.endDate && req.body.endDate > req.timeline.endDate) { error = 'Milestone endDate must not be after the timeline endDate'; } if (error) { @@ -90,9 +88,24 @@ module.exports = [ }, transaction: tx, }); + }) + .then((updatedCount) => { + if (updatedCount) { + return models.Milestone.findAll({ + where: { + timelineId: result.timelineId, + id: { $ne: result.id }, + order: { $gte: result.order + 1 }, + }, + order: [['updatedAt', 'DESC']], + limit: updatedCount[0], + transaction: tx, + }); + } + return Promise.resolve(); }), ) - .then(() => { + .then((otherUpdated) => { // Do not send events for the updated milestones here, // because it will make 'version conflict' error in ES. // The order of the other milestones need to be updated in the MILESTONE_ADDED event handler @@ -104,11 +117,25 @@ module.exports = [ { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.MILESTONE_ADDED, - { req, created: result }); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_ADDED, + RESOURCES.MILESTONE, + result); + + + // emit the event for other milestone order updated + _.map(otherUpdated, milestone => + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_UPDATED, + RESOURCES.MILESTONE, + _.assign(_.pick(milestone.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt'))), + ); // Write to the response - res.status(201).json(util.wrapResponse(req.id, result, 1, 201)); + res.status(201).json(result); return Promise.resolve(); }) .catch(next); diff --git a/src/routes/milestones/create.spec.js b/src/routes/milestones/create.spec.js index 606cfda5..8d9163b1 100644 --- a/src/routes/milestones/create.spec.js +++ b/src/routes/milestones/create.spec.js @@ -10,7 +10,7 @@ import server from '../../app'; import testUtil from '../../tests/util'; import models from '../../models'; import busApi from '../../services/busApi'; -import { EVENT, BUS_API_EVENT } from '../../constants'; +import { EVENT, RESOURCES, BUS_API_EVENT } from '../../constants'; const should = chai.should(); @@ -206,32 +206,30 @@ describe('CREATE milestone', () => { describe('POST /timelines/{timelineId}/milestones', () => { const body = { - param: { - name: 'milestone 4', - description: 'description 4', - duration: 4, - startDate: '2018-05-05T00:00:00.000Z', - endDate: '2018-05-07T00:00:00.000Z', - completionDate: '2018-05-08T00:00:00.000Z', - status: 'open', - type: 'type4', - details: { - detail1: { - subDetail1C: 4, - }, - detail2: [ - 3, - 4, - 5, - ], + name: 'milestone 4', + description: 'description 4', + duration: 4, + startDate: '2018-05-05T00:00:00.000Z', + endDate: '2018-05-07T00:00:00.000Z', + completionDate: '2018-05-08T00:00:00.000Z', + status: 'open', + type: 'type4', + details: { + detail1: { + subDetail1C: 4, }, - order: 2, - plannedText: 'plannedText 4', - activeText: 'activeText 4', - completedText: 'completedText 4', - blockedText: 'blockedText 4', - hidden: true, + detail2: [ + 3, + 4, + 5, + ], }, + order: 2, + plannedText: 'plannedText 4', + activeText: 'activeText 4', + completedText: 'completedText 4', + blockedText: 'blockedText 4', + hidden: true, }; it('should return 403 if user is not authenticated', (done) => { @@ -252,11 +250,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if missing name', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - name: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + name: undefined, + }); request(server) .post('/v5/timelines/1/milestones') @@ -269,11 +265,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if missing duration', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - duration: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + duration: undefined, + }); request(server) .post('/v5/timelines/1/milestones') @@ -286,11 +280,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if missing type', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - type: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + type: undefined, + }); request(server) .post('/v5/timelines/1/milestones') @@ -303,11 +295,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if missing order', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - order: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + order: undefined, + }); request(server) .post('/v5/timelines/1/milestones') @@ -320,11 +310,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if missing plannedText', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - plannedText: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + plannedText: undefined, + }); request(server) .post('/v5/timelines/1/milestones') @@ -337,11 +325,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if missing activeText', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - activeText: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + activeText: undefined, + }); request(server) .post('/v5/timelines/1/milestones') @@ -354,11 +340,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if missing completedText', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - completedText: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + completedText: undefined, + }); request(server) .post('/v5/timelines/1/milestones') @@ -371,11 +355,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if missing blockedText', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - blockedText: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + blockedText: undefined, + }); request(server) .post('/v5/timelines/1/milestones') @@ -388,12 +370,10 @@ describe('CREATE milestone', () => { }); it('should return 400 if startDate is after endDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: '2018-05-29T00:00:00.000Z', - endDate: '2018-05-28T00:00:00.000Z', - }), - }; + const invalidBody = _.assign({}, body, { + startDate: '2018-05-29T00:00:00.000Z', + endDate: '2018-05-28T00:00:00.000Z', + }); request(server) .post('/v5/timelines/1/milestones') @@ -406,12 +386,10 @@ describe('CREATE milestone', () => { }); it('should return 400 if startDate is after completionDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: '2018-05-29T00:00:00.000Z', - completionDate: '2018-05-28T00:00:00.000Z', - }), - }; + const invalidBody = _.assign({}, body, { + startDate: '2018-05-29T00:00:00.000Z', + completionDate: '2018-05-28T00:00:00.000Z', + }); request(server) .post('/v5/timelines/1/milestones') @@ -424,11 +402,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if startDate is before the timeline startDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: '2018-05-01T00:00:00.000Z', - }), - }; + const invalidBody = _.assign({}, body, { + startDate: '2018-05-01T00:00:00.000Z', + }); request(server) .post('/v5/timelines/1/milestones') @@ -441,11 +417,9 @@ describe('CREATE milestone', () => { }); it('should return 400 if endDate is after the timeline endDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - endDate: '2018-06-13T00:00:00.000Z', - }), - }; + const invalidBody = _.assign({}, body, { + endDate: '2018-06-13T00:00:00.000Z', + }); request(server) .post('/v5/timelines/1/milestones') @@ -500,23 +474,23 @@ describe('CREATE milestone', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(body.param.name); - resJson.description.should.be.eql(body.param.description); - resJson.duration.should.be.eql(body.param.duration); - resJson.startDate.should.be.eql(body.param.startDate); - resJson.endDate.should.be.eql(body.param.endDate); - resJson.completionDate.should.be.eql(body.param.completionDate); - resJson.status.should.be.eql(body.param.status); - resJson.type.should.be.eql(body.param.type); - resJson.details.should.be.eql(body.param.details); - resJson.order.should.be.eql(body.param.order); - resJson.plannedText.should.be.eql(body.param.plannedText); - resJson.activeText.should.be.eql(body.param.activeText); - resJson.completedText.should.be.eql(body.param.completedText); - resJson.blockedText.should.be.eql(body.param.blockedText); - resJson.hidden.should.be.eql(body.param.hidden); + resJson.name.should.be.eql(body.name); + resJson.description.should.be.eql(body.description); + resJson.duration.should.be.eql(body.duration); + resJson.startDate.should.be.eql(body.startDate); + resJson.endDate.should.be.eql(body.endDate); + resJson.completionDate.should.be.eql(body.completionDate); + resJson.status.should.be.eql(body.status); + resJson.type.should.be.eql(body.type); + resJson.details.should.be.eql(body.details); + resJson.order.should.be.eql(body.order); + resJson.plannedText.should.be.eql(body.plannedText); + resJson.activeText.should.be.eql(body.activeText); + resJson.completedText.should.be.eql(body.completedText); + resJson.blockedText.should.be.eql(body.blockedText); + resJson.hidden.should.be.eql(body.hidden); resJson.createdBy.should.be.eql(40051333); // admin should.exist(resJson.createdAt); @@ -556,7 +530,7 @@ describe('CREATE milestone', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051334); // manager resJson.updatedBy.should.be.eql(40051334); // manager done(); @@ -573,7 +547,7 @@ describe('CREATE milestone', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051336); // connect admin resJson.updatedBy.should.be.eql(40051336); // connect admin done(); @@ -590,7 +564,7 @@ describe('CREATE milestone', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051332); // copilot resJson.updatedBy.should.be.eql(40051332); // copilot done(); @@ -607,7 +581,7 @@ describe('CREATE milestone', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051331); // member resJson.updatedBy.should.be.eql(40051331); // member done(); @@ -631,7 +605,7 @@ describe('CREATE milestone', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when milestone created', (done) => { + it('should send message BUS_API_EVENT.MILESTONE_ADDED when milestone created', (done) => { request(server) .post('/v5/timelines/1/milestones') .set({ @@ -645,14 +619,13 @@ describe('CREATE milestone', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_ADDED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.callCount.should.be.eql(3); + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_ADDED, + sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_ADDED, + sinon.match({ name: 'milestone 4' })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_ADDED, + sinon.match({ description: 'description 4' })).should.be.true; done(); }); } diff --git a/src/routes/milestones/delete.js b/src/routes/milestones/delete.js index 45e5a41b..78b9a3e5 100644 --- a/src/routes/milestones/delete.js +++ b/src/routes/milestones/delete.js @@ -5,7 +5,8 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; -import { EVENT } from '../../constants'; +import util from '../../util'; +import { EVENT, RESOURCES } from '../../constants'; import validateTimeline from '../../middlewares/validateTimeline'; const permissions = tcMiddleware.permissions; @@ -46,8 +47,7 @@ module.exports = [ // Update the deletedBy, and soft delete return milestone.update({ deletedBy: req.authUser.userId }, { transaction: tx }) .then(() => milestone.destroy({ transaction: tx })); - }), - ) + }) .then((deleted) => { // Send event to bus req.log.debug('Sending event to RabbitMQ bus for milestone %d', deleted.id); @@ -55,13 +55,18 @@ module.exports = [ deleted, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.MILESTONE_REMOVED, - { req, deleted }); + + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_REMOVED, + RESOURCES.MILESTONE, + { id: deleted.id }); // Write to response res.status(204).end(); return Promise.resolve(); }) - .catch(next); + .catch(next)); }, ]; diff --git a/src/routes/milestones/delete.spec.js b/src/routes/milestones/delete.spec.js index 2ee6010f..edbc2fb7 100644 --- a/src/routes/milestones/delete.spec.js +++ b/src/routes/milestones/delete.spec.js @@ -9,11 +9,14 @@ import chai from 'chai'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; -import { EVENT, BUS_API_EVENT } from '../../constants'; +import { EVENT, RESOURCES, BUS_API_EVENT } from '../../constants'; import busApi from '../../services/busApi'; +const should = chai.should(); // eslint-disable-line no-unused-vars + const expectAfterDelete = (timelineId, id, err, next) => { if (err) throw err; + setTimeout(() => models.Milestone.findOne({ where: { timelineId, @@ -27,15 +30,9 @@ const expectAfterDelete = (timelineId, id, err, next) => { } else { chai.assert.isNotNull(res.deletedAt); chai.assert.isNotNull(res.deletedBy); - - request(server) - .get(`/v5/timelines/${timelineId}/milestones/${id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect(404, next); } - }); + next(); + }), 500); }; describe('DELETE milestone', () => { @@ -156,7 +153,7 @@ describe('DELETE milestone', () => { deletedAt: '2018-05-14T00:00:00.000Z', }, ])) - .then(() => { + .then(() => // Create milestones models.Milestone.bulkCreate([ { @@ -213,13 +210,13 @@ describe('DELETE milestone', () => { deletedBy: 1, deletedAt: '2018-05-04T00:00:00.000Z', }, - ]) - .then(() => done()); - }); + ])) + .then(() => done()); }); }); }); + after((done) => { testUtil.clearDb(done); }); @@ -374,9 +371,7 @@ describe('DELETE milestone', () => { sandbox.restore(); }); - // not testing fields separately as startDate is required parameter, - // thus TIMELINE_ADJUSTED will be always sent - it('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when milestone removed', (done) => { + it('should send message BUS_API_EVENT.MILESTONE_REMOVED when milestone removed', (done) => { request(server) .delete('/v5/timelines/1/milestones/1') .set({ @@ -389,13 +384,10 @@ describe('DELETE milestone', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_REMOVED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_REMOVED, + sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_REMOVED, + sinon.match({ id: 1 })).should.be.true; done(); }); } diff --git a/src/routes/milestones/get.js b/src/routes/milestones/get.js index a4731321..5c33994d 100644 --- a/src/routes/milestones/get.js +++ b/src/routes/milestones/get.js @@ -30,20 +30,40 @@ module.exports = [ id: req.params.milestoneId, }; - // Find the milestone - models.Milestone.findOne({ where }) - .then((milestone) => { - // Not found - if (!milestone) { - const apiErr = new Error(`Milestone not found for milestone id ${req.params.milestoneId}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + util.fetchByIdFromES('milestones', { + query: { + nested: { + path: 'milestones', + query: { + match: { 'milestones.id': req.params.milestoneId }, + }, + inner_hits: {}, + }, + }, + }, 'timeline') + .then((data) => { + if (data.length === 0) { + req.log.debug('No milestone found in ES'); + // Find the milestone + models.Milestone.findOne({ where }) + .then((milestone) => { + // Not found + if (!milestone) { + const apiErr = new Error(`Milestone not found for milestone id ${req.params.milestoneId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } - // Write to response - res.json(util.wrapResponse(req.id, _.omit(milestone.toJSON(), ['deletedBy', 'deletedAt']))); - return Promise.resolve(); - }) - .catch(next); + // Write to response + res.json(_.omit(milestone.toJSON(), ['deletedBy', 'deletedAt'])); + return Promise.resolve(); + }) + .catch(next); + } else { + req.log.debug('milestone found in ES'); + res.json(data[0].inner_hits.milestones.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + } + }) + .catch(next); }, ]; diff --git a/src/routes/milestones/get.spec.js b/src/routes/milestones/get.spec.js index ebbfb9d7..1c6797da 100644 --- a/src/routes/milestones/get.spec.js +++ b/src/routes/milestones/get.spec.js @@ -275,7 +275,7 @@ describe('GET milestone', () => { }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(1); resJson.timelineId.should.be.eql(1); resJson.name.should.be.eql('milestone 1'); diff --git a/src/routes/milestones/list.js b/src/routes/milestones/list.js index 54197b01..ab7a4f82 100644 --- a/src/routes/milestones/list.js +++ b/src/routes/milestones/list.js @@ -28,7 +28,7 @@ module.exports = [ permissions('milestone.view'), (req, res, next) => { // Parse the sort query - let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'order'; + let sort = req.query.sort ? req.query.sort : 'order'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } @@ -49,12 +49,7 @@ module.exports = [ id: req.params.timelineId, }) .then((doc) => { - if (!doc) { - const err = new Error(`Timeline not found for timeline id ${req.params.timelineId}`); - err.status = 404; - throw err; - } - + req.log.debug('milestone found in ES'); // Get the milestones let milestones = _.isArray(doc._source.milestones) ? doc._source.milestones : []; // eslint-disable-line no-underscore-dangle @@ -62,8 +57,19 @@ module.exports = [ milestones = _.orderBy(milestones, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]); // Write to response - res.json(util.wrapResponse(req.id, milestones, milestones.length)); + res.json(milestones); }) - .catch(err => next(err)); + .catch((err) => { + if (err.status === 404) { + req.log.debug('No milestone found in ES'); + // Load the milestones + return req.timeline.getMilestones() + .then(milestones => + // Write to response + res.json(_.map(milestones, milestone => _.omit(milestone.toJSON(), ['deletedAt', 'deletedBy']))), + ); + } + return next(err); + }); }, ]; diff --git a/src/routes/milestones/list.spec.js b/src/routes/milestones/list.spec.js index 04c66a30..570e426f 100644 --- a/src/routes/milestones/list.spec.js +++ b/src/routes/milestones/list.spec.js @@ -79,7 +79,7 @@ const milestones = [ }, ]; -describe('LIST timelines', () => { +describe('LIST milestones', () => { before(function beforeHook(done) { this.timeout(10000); testUtil.clearDb() @@ -241,7 +241,7 @@ describe('LIST timelines', () => { }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].should.be.eql(milestones[0]); @@ -259,7 +259,7 @@ describe('LIST timelines', () => { }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); done(); @@ -274,7 +274,7 @@ describe('LIST timelines', () => { }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); done(); @@ -288,7 +288,7 @@ describe('LIST timelines', () => { Authorization: `Bearer ${testUtil.jwts.member}`, }) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); done(); @@ -302,7 +302,7 @@ describe('LIST timelines', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); done(); @@ -317,7 +317,7 @@ describe('LIST timelines', () => { }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(2); resJson[0].should.be.eql(milestones[1]); diff --git a/src/routes/milestones/update.js b/src/routes/milestones/update.js index 6d8b053d..6ed6b47c 100644 --- a/src/routes/milestones/update.js +++ b/src/routes/milestones/update.js @@ -9,7 +9,7 @@ import Sequelize from 'sequelize'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; import validateTimeline from '../../middlewares/validateTimeline'; -import { EVENT, MILESTONE_STATUS } from '../../constants'; +import { EVENT, RESOURCES, MILESTONE_STATUS } from '../../constants'; import models from '../../models'; const permissions = tcMiddleware.permissions; @@ -92,33 +92,31 @@ const schema = { timelineId: Joi.number().integer().positive().required(), milestoneId: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - name: Joi.string().max(255).optional(), - description: Joi.string().max(255), - duration: Joi.number().integer().min(1).optional(), - startDate: Joi.any().forbidden(), - actualStartDate: Joi.date().allow(null), - endDate: Joi.any().forbidden(), - completionDate: Joi.date().allow(null), - status: Joi.string().max(45).optional(), - type: Joi.string().max(45).optional(), - details: Joi.object(), - order: Joi.number().integer().optional(), - plannedText: Joi.string().max(512).optional(), - activeText: Joi.string().max(512).optional(), - completedText: Joi.string().max(512).optional(), - blockedText: Joi.string().max(512).optional(), - hidden: Joi.boolean().optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + id: Joi.any().strip(), + name: Joi.string().max(255).optional(), + description: Joi.string().max(255), + duration: Joi.number().integer().min(1).optional(), + startDate: Joi.any().forbidden(), + actualStartDate: Joi.date().allow(null), + endDate: Joi.any().forbidden(), + completionDate: Joi.date().allow(null), + status: Joi.string().max(45).optional(), + type: Joi.string().max(45).optional(), + details: Joi.object(), + order: Joi.number().integer().optional(), + plannedText: Joi.string().max(512).optional(), + activeText: Joi.string().max(512).optional(), + completedText: Joi.string().max(512).optional(), + blockedText: Joi.string().max(512).optional(), + hidden: Joi.boolean().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -132,7 +130,7 @@ module.exports = [ timelineId: req.params.timelineId, id: req.params.milestoneId, }; - const entityToUpdate = _.assign(req.body.param, { + const entityToUpdate = _.assign(req.body, { updatedBy: req.authUser.userId, timelineId: req.params.timelineId, }); @@ -296,15 +294,16 @@ module.exports = [ { correlationId: req.id }, ); - // Do not send events for the the other milestones (updated order) here, - // because it will make 'version conflict' error in ES. - // The order of the other milestones need to be updated in the MILESTONE_UPDATED event above - - req.app.emit(EVENT.ROUTING_KEY.MILESTONE_UPDATED, - { req, original, updated, cascadedUpdates }); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.MILESTONE_UPDATED, + RESOURCES.MILESTONE, + _.assign(entityToUpdate, _.pick(updated, 'id', 'updatedAt')), + ); // Write to response - res.json(util.wrapResponse(req.id, updated)); + res.json(updated); return Promise.resolve(); }) .catch(next); diff --git a/src/routes/milestones/update.spec.js b/src/routes/milestones/update.spec.js index 06a1d060..056e884d 100644 --- a/src/routes/milestones/update.spec.js +++ b/src/routes/milestones/update.spec.js @@ -11,7 +11,7 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { EVENT, MILESTONE_STATUS, BUS_API_EVENT } from '../../constants'; +import { EVENT, RESOURCES, MILESTONE_STATUS, BUS_API_EVENT } from '../../constants'; const should = chai.should(); @@ -263,28 +263,26 @@ describe('UPDATE Milestone', () => { describe('PATCH /timelines/{timelineId}/milestones/{milestoneId}', () => { const body = { - param: { - name: 'Milestone 1-updated', - duration: 3, - completionDate: '2018-05-16T00:00:00.000Z', - description: 'description-updated', - status: 'closed', - type: 'type1-updated', - details: { - detail1: { - subDetail1A: 0, - subDetail1C: 3, - }, - detail2: [4], - detail3: 3, + name: 'Milestone 1-updated', + duration: 3, + completionDate: '2018-05-16T00:00:00.000Z', + description: 'description-updated', + status: 'closed', + type: 'type1-updated', + details: { + detail1: { + subDetail1A: 0, + subDetail1C: 3, }, - order: 1, - plannedText: 'plannedText 1-updated', - activeText: 'activeText 1-updated', - completedText: 'completedText 1-updated', - blockedText: 'blockedText 1-updated', - hidden: true, + detail2: [4], + detail3: 3, }, + order: 1, + plannedText: 'plannedText 1-updated', + activeText: 'activeText 1-updated', + completedText: 'completedText 1-updated', + blockedText: 'blockedText 1-updated', + hidden: true, }; it('should return 403 if user is not authenticated', (done) => { @@ -366,7 +364,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for missing name', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.name; + delete partialBody.name; request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -378,7 +376,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for missing type', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.type; + delete partialBody.type; request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -390,7 +388,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for missing duration', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.duration; + delete partialBody.duration; request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -402,7 +400,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for missing order', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.order; + delete partialBody.order; request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -414,7 +412,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for missing plannedText', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.plannedText; + delete partialBody.plannedText; request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -426,7 +424,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for missing blockedText', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.blockedText; + delete partialBody.blockedText; request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -438,7 +436,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for missing activeText', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.activeText; + delete partialBody.activeText; request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -450,7 +448,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for missing completedText', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.completedText; + delete partialBody.completedText; request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -462,7 +460,7 @@ describe('UPDATE Milestone', () => { it('should return 200 for missing hidden field', (done) => { const partialBody = _.cloneDeep(body); - delete partialBody.param.hidden; + delete partialBody.hidden; request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -474,11 +472,9 @@ describe('UPDATE Milestone', () => { ['startDate', 'endDate'].forEach((field) => { it(`should return 400 if ${field} is present in the payload`, (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - [field]: '2018-07-01T00:00:00.000Z', - }), - }; + const invalidBody = _.assign({}, body, { + [field]: '2018-07-01T00:00:00.000Z', + }); request(server) .patch('/v5/timelines/1/milestones/1') @@ -500,24 +496,24 @@ describe('UPDATE Milestone', () => { .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(body.param.name); - resJson.description.should.be.eql(body.param.description); - resJson.duration.should.be.eql(body.param.duration); - resJson.completionDate.should.be.eql(body.param.completionDate); - resJson.status.should.be.eql(body.param.status); - resJson.type.should.be.eql(body.param.type); + resJson.name.should.be.eql(body.name); + resJson.description.should.be.eql(body.description); + resJson.duration.should.be.eql(body.duration); + resJson.completionDate.should.be.eql(body.completionDate); + resJson.status.should.be.eql(body.status); + resJson.type.should.be.eql(body.type); resJson.details.should.be.eql({ detail1: { subDetail1A: 0, subDetail1B: 2, subDetail1C: 3 }, detail2: [4], detail3: 3, }); - resJson.order.should.be.eql(body.param.order); - resJson.plannedText.should.be.eql(body.param.plannedText); - resJson.activeText.should.be.eql(body.param.activeText); - resJson.completedText.should.be.eql(body.param.completedText); - resJson.blockedText.should.be.eql(body.param.blockedText); + resJson.order.should.be.eql(body.order); + resJson.plannedText.should.be.eql(body.plannedText); + resJson.activeText.should.be.eql(body.activeText); + resJson.completedText.should.be.eql(body.completedText); + resJson.blockedText.should.be.eql(body.blockedText); should.exist(resJson.createdBy); should.exist(resJson.createdAt); @@ -542,7 +538,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 4 }) }) // 1 to 4 + .send(_.assign({}, body, { order: 4 })) // 1 to 4 .expect(200) .end(() => { // Milestone 1: order 4 @@ -579,7 +575,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 5 }) }) // 1 to 5 + .send(_.assign({}, body, { order: 5 })) // 1 to 5 .expect(200) .end(() => { // Milestone 1: order 5 @@ -616,7 +612,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 2 }) }) // 4 to 2 + .send(_.assign({}, body, { order: 2 })) // 4 to 2 .expect(200) .end(() => { // Milestone 1: order 1 @@ -653,7 +649,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 0 }) }) // 4 to 0 + .send(_.assign({}, body, { order: 0 })) // 4 to 0 .expect(200) .end(() => { // Milestone 1: order 1 @@ -690,7 +686,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 0 }) }) // 1 to 0 + .send(_.assign({}, body, { order: 0 })) // 1 to 0 .expect(200) .end(() => { // Milestone 6: order 0 @@ -752,7 +748,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 2 }) }) // 4 to 2 + .send(_.assign({}, body, { order: 2 })) // 4 to 2 .expect(200) .end(() => { // Milestone 6: order 1 => 1 @@ -825,7 +821,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { order: 2 }) }) // 4 to 2 + .send(_.assign({}, body, { order: 2 })) // 4 to 2 .expect(200) .end(() => { // Milestone 6: order 1 => 1 @@ -862,7 +858,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: { status: MILESTONE_STATUS.ACTIVE } }) + .send({ status: MILESTONE_STATUS.ACTIVE }) .expect(200) .end(() => { // Milestone 2: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-14T00:00:00.000Z' @@ -916,9 +912,9 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { + .send(_.assign({}, body, { completionDate: '2018-05-18T00:00:00.000Z', order: undefined, duration: undefined, - }) }) + })) .expect(200) .end(() => { // Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z' @@ -955,9 +951,9 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { + .send(_.assign({}, body, { completionDate: '2018-05-18T00:00:00.000Z', order: undefined, duration: undefined, - }) }) + })) .expect(200) .end(() => { // Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z' @@ -989,7 +985,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { duration: 5, order: undefined, completionDate: undefined }) }) + .send(_.assign({}, body, { duration: 5, order: undefined, completionDate: undefined })) .expect(200) .end(() => { // Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z' @@ -1021,7 +1017,7 @@ describe('UPDATE Milestone', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ param: _.assign({}, body.param, { duration: 5, order: undefined, completionDate: undefined }) }) + .send(_.assign({}, body, { duration: 5, order: undefined, completionDate: undefined })) .expect(200) .end(() => { // Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z' @@ -1104,18 +1100,16 @@ describe('UPDATE Milestone', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.MILESTONE_WAITING_CUSTOMER when milestone duration updated', (done) => { + it('should send message BUS_API_EVENT.MILESTONE_UPDATED when milestone duration updated', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - // duration: 1, - details: { - metadata: { waitingForCustomer: true }, - }, + // duration: 1, + details: { + metadata: { waitingForCustomer: true }, }, }) .expect(200) @@ -1127,31 +1121,29 @@ describe('UPDATE Milestone', () => { // 5 milestones in total, so it would trigger 5 events // 4 MILESTONE_UPDATED events are for 4 non deleted milestones // 1 TIMELINE_ADJUSTED event, because timeline's end date updated - createEventSpy.callCount.should.be.eql(3); - createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; - // createEventSpy.lastCall.calledWith(BUS_API_EVENT.MILESTONE_WAITING_CUSTOMER).should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ + details: { + metadata: { waitingForCustomer: true }, + }, + })).should.be.true; done(); }); } }); }); - xit('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when milestone duration updated', (done) => { + xit('should send message BUS_API_EVENT.MILESTONE_UPDATED when milestone duration updated', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - duration: 1, - }, + duration: 1, }) .expect(200) .end((err) => { @@ -1162,15 +1154,11 @@ describe('UPDATE Milestone', () => { // 5 milestones in total, so it would trigger 5 events // 4 MILESTONE_UPDATED events are for 4 non deleted milestones // 1 TIMELINE_ADJUSTED event, because timeline's end date updated - createEventSpy.callCount.should.be.eql(5); - createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; - createEventSpy.lastCall.calledWith(BUS_API_EVENT.TIMELINE_ADJUSTED).should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ duration: 1 })).should.be.true; done(); }); } @@ -1184,9 +1172,7 @@ describe('UPDATE Milestone', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - status: 'reviewed', - }, + status: 'reviewed', }) .expect(200) .end((err) => { @@ -1195,13 +1181,10 @@ describe('UPDATE Milestone', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ status: 'reviewed' })).should.be.true; done(); }); } @@ -1215,9 +1198,7 @@ describe('UPDATE Milestone', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - order: 2, - }, + order: 2, }) .expect(200) .end((err) => { @@ -1225,14 +1206,11 @@ describe('UPDATE Milestone', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ order: 2 })).should.be.true; done(); }); } @@ -1246,9 +1224,7 @@ describe('UPDATE Milestone', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - plannedText: 'new text', - }, + plannedText: 'new text', }) .expect(200) .end((err) => { @@ -1256,14 +1232,11 @@ describe('UPDATE Milestone', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, + sinon.match({ plannedText: 'new text' })).should.be.true; done(); }); } diff --git a/src/routes/orgConfig/get.js b/src/routes/orgConfig/get.js index 1ad68306..5bd8d582 100644 --- a/src/routes/orgConfig/get.js +++ b/src/routes/orgConfig/get.js @@ -29,7 +29,7 @@ module.exports = [ inner_hits: {}, }, }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No orgConfig found in ES'); diff --git a/src/routes/orgConfig/list.js b/src/routes/orgConfig/list.js index bb99e4d0..b77b3dc3 100644 --- a/src/routes/orgConfig/list.js +++ b/src/routes/orgConfig/list.js @@ -11,7 +11,8 @@ const permissions = tcMiddleware.permissions; const schema = { query: { - filter: Joi.string().required(), + orgId: Joi.string().required(), + configName: Joi.string().optional(), }, }; @@ -22,7 +23,7 @@ module.exports = [ util.fetchFromES('orgConfigs') .then((data) => { // handle filters - const filters = util.parseQueryFilter(req.query.filter); + const filters = req.query; // Throw error if orgId is not present in filter if (!filters.orgId) { next(util.buildApiError('Missing filter orgId', 400)); diff --git a/src/routes/orgConfig/list.spec.js b/src/routes/orgConfig/list.spec.js index 14ae71c1..69317422 100644 --- a/src/routes/orgConfig/list.spec.js +++ b/src/routes/orgConfig/list.spec.js @@ -42,7 +42,7 @@ describe('LIST organization config', () => { describe('GET /orgConfig', () => { it('should return 200 for admin with filter', (done) => { request(server) - .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -68,13 +68,13 @@ describe('LIST organization config', () => { it('should return 403 if user is not authenticated with filter', (done) => { request(server) - .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) .expect(403, done); }); it('should return 200 for connect admin with filter', (done) => { request(server) - .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -84,7 +84,7 @@ describe('LIST organization config', () => { it('should return 200 for connect manager with filter', (done) => { request(server) - .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) @@ -94,7 +94,7 @@ describe('LIST organization config', () => { it('should return 200 for member with filter', (done) => { request(server) - .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) @@ -103,7 +103,7 @@ describe('LIST organization config', () => { it('should return 200 for copilot with filter', (done) => { request(server) - .get(`${orgConfigPath}?filter=orgId%3Din%28${configs[0].orgId}%29%26configName=${configs[0].configName}`) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) diff --git a/src/routes/phaseProducts/create.js b/src/routes/phaseProducts/create.js index 10887006..4bfb9bc3 100644 --- a/src/routes/phaseProducts/create.js +++ b/src/routes/phaseProducts/create.js @@ -6,23 +6,21 @@ import Joi from 'joi'; import models from '../../models'; import util from '../../util'; -import { EVENT } from '../../constants'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = require('tc-core-library-js').middleware.permissions; const addPhaseProductValidations = { - body: { - param: Joi.object().keys({ - name: Joi.string().required(), - type: Joi.string().required(), - templateId: Joi.number().positive().optional(), - directProjectId: Joi.number().positive().optional(), - billingAccountId: Joi.number().positive().optional(), - estimatedPrice: Joi.number().positive().optional(), - actualPrice: Joi.number().positive().optional(), - details: Joi.any().optional(), - }).required(), - }, + body: Joi.object().keys({ + name: Joi.string().required(), + type: Joi.string().required(), + templateId: Joi.number().positive().optional(), + directProjectId: Joi.number().positive().optional(), + billingAccountId: Joi.number().positive().optional(), + estimatedPrice: Joi.number().positive().optional(), + actualPrice: Joi.number().positive().optional(), + details: Joi.any().optional(), + }).required(), }; module.exports = [ @@ -35,7 +33,7 @@ module.exports = [ const projectId = _.parseInt(req.params.projectId); const phaseId = _.parseInt(req.params.phaseId); - const data = req.body.param; + const data = req.body; // default values _.assign(data, { createdBy: req.authUser.userId, @@ -107,10 +105,14 @@ module.exports = [ newPhaseProduct, { correlationId: req.id }, ); - req.log.debug('Sending event to Kafka bus for phase product %d', newPhaseProduct.id); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, { req, created: newPhaseProduct }); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, + RESOURCES.PHASE_PRODUCT, + newPhaseProduct); - res.status(201).json(util.wrapResponse(req.id, newPhaseProduct, 1, 201)); + res.status(201).json(newPhaseProduct); }) .catch((err) => { next(err); }); }, diff --git a/src/routes/phaseProducts/create.spec.js b/src/routes/phaseProducts/create.spec.js index e28dc661..1e51d383 100644 --- a/src/routes/phaseProducts/create.spec.js +++ b/src/routes/phaseProducts/create.spec.js @@ -105,7 +105,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(403, done); }); @@ -116,7 +116,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(403, done); }); @@ -129,7 +129,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: reqBody }) + .send(reqBody) .expect('Content-Type', /json/) .expect(400, done); }); @@ -142,7 +142,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: reqBody }) + .send(reqBody) .expect('Content-Type', /json/) .expect(400, done); }); @@ -155,7 +155,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: reqBody }) + .send(reqBody) .expect('Content-Type', /json/) .expect(400, done); }); @@ -168,7 +168,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: reqBody }) + .send(reqBody) .expect('Content-Type', /json/) .expect(400, done); }); @@ -179,7 +179,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(404, done); }); @@ -190,7 +190,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(404, done); }); @@ -201,14 +201,14 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.be.eql(body.name); resJson.type.should.be.eql(body.type); @@ -237,13 +237,13 @@ describe('Phase Products', () => { sandbox.restore(); }); - it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when product phase created', (done) => { + it('should not send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_ADDED when product phase created', (done) => { request(server) .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(201) .end((err) => { @@ -251,7 +251,7 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.notCalled.should.be.true; + createEventSpy.calledOnce.should.be.true; done(); }); } diff --git a/src/routes/phaseProducts/delete.js b/src/routes/phaseProducts/delete.js index b8efbb39..c30138bb 100644 --- a/src/routes/phaseProducts/delete.js +++ b/src/routes/phaseProducts/delete.js @@ -3,7 +3,8 @@ import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; -import { EVENT } from '../../constants'; +import util from '../../util'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -45,7 +46,12 @@ module.exports = [ deleted, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, { req, deleted }); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, + RESOURCES.PHASE_PRODUCT, + _.pick(deleted.toJSON(), 'id')); res.status(204).json({}); }) diff --git a/src/routes/phaseProducts/delete.spec.js b/src/routes/phaseProducts/delete.spec.js index ea5da981..dbbd677f 100644 --- a/src/routes/phaseProducts/delete.spec.js +++ b/src/routes/phaseProducts/delete.spec.js @@ -8,31 +8,27 @@ import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; +const should = chai.should(); // eslint-disable-line no-unused-vars + const expectAfterDelete = (projectId, phaseId, id, err, next) => { if (err) throw err; setTimeout(() => - models.PhaseProduct.findOne({ - where: { - id, - projectId, - phaseId, - }, - paranoid: false, - }) + models.PhaseProduct.findOne({ + where: { + id, + projectId, + phaseId, + }, + paranoid: false, + }) .then((res) => { if (!res) { throw new Error('Should found the entity'); } else { chai.assert.isNotNull(res.deletedAt); chai.assert.isNotNull(res.deletedBy); - - request(server) - .get(`/v5/projects/${projectId}/phases/${phaseId}/products/${id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect(404, next); } + next(); }), 500); }; const body = { @@ -209,7 +205,7 @@ describe('Phase Products', () => { sandbox.restore(); }); - it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when product phase removed', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_REMOVED when product phase removed', (done) => { request(server) .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ @@ -221,7 +217,7 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.notCalled.should.be.true; + createEventSpy.calledOnce.should.be.true; done(); }); } diff --git a/src/routes/phaseProducts/get.js b/src/routes/phaseProducts/get.js index 30aa2ed4..a2eca0c7 100644 --- a/src/routes/phaseProducts/get.js +++ b/src/routes/phaseProducts/get.js @@ -15,22 +15,63 @@ module.exports = [ const phaseId = _.parseInt(req.params.phaseId); const productId = _.parseInt(req.params.productId); - return models.PhaseProduct.findOne({ - where: { - id: productId, - projectId, - phaseId, + // Get project from ES + return util.fetchByIdFromES('phaseProducts', { + query: { + nested: { + path: 'phases', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'phases.id': phaseId } }, + { term: { 'phases.projectId': projectId } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, }, - }).then((product) => { - if (!product) { - // handle 404 - const err = new Error('phase product not found for project id ' + - `${projectId}, phase id ${phaseId} and product id ${productId}`); - err.status = 404; - throw err; - } else { - res.json(util.wrapResponse(req.id, product)); - } - }).catch(err => next(err)); + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No phase product found in ES'); + return models.PhaseProduct.findOne({ + where: { + id: productId, + projectId, + phaseId, + }, + }).then((product) => { + if (!product) { + // handle 404 + const err = new Error('phase product not found for project id ' + + `${projectId}, phase id ${phaseId} and product id ${productId}`); + err.status = 404; + throw err; + } else { + res.json(product); + } + }).catch(err => next(err)); + } + req.log.debug('phase product found in ES'); + // Get the phases + const phases = data[0].inner_hits.phases.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle + const product = _.isArray(phases.products) ? _.find(phases.products, p => p.id === productId) : {}; + if (!product) { + // handle 404 + const err = new Error('phase product not found for project id ' + + `${projectId}, phase id ${phaseId} and product id ${productId}`); + err.status = 404; + throw err; + } + + return res.json(product); + }) + .catch(err => next(err)); }, ]; diff --git a/src/routes/phaseProducts/get.spec.js b/src/routes/phaseProducts/get.spec.js index 5ab6b4fe..2160fd61 100644 --- a/src/routes/phaseProducts/get.spec.js +++ b/src/routes/phaseProducts/get.spec.js @@ -157,7 +157,7 @@ describe('Phase Products', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.be.eql(body.name); resJson.type.should.be.eql(body.type); @@ -181,7 +181,7 @@ describe('Phase Products', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.be.eql(body.name); resJson.type.should.be.eql(body.type); diff --git a/src/routes/phaseProducts/list-db.js b/src/routes/phaseProducts/list-db.js index d0eab2f5..64e14db4 100644 --- a/src/routes/phaseProducts/list-db.js +++ b/src/routes/phaseProducts/list-db.js @@ -1,6 +1,5 @@ import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import models from '../../models'; const permissions = tcMiddleware.permissions; @@ -36,8 +35,8 @@ module.exports = [ }; try { - const { rows, count } = await models.PhaseProduct.search(parameters, req.log); - return res.json(util.wrapResponse(req.id, rows, count)); + const { rows } = await models.PhaseProduct.search(parameters, req.log); + return res.json(rows); } catch (err) { return next(err); } diff --git a/src/routes/phaseProducts/list-db.spec.js b/src/routes/phaseProducts/list-db.spec.js index e78f4b23..47de2c28 100644 --- a/src/routes/phaseProducts/list-db.spec.js +++ b/src/routes/phaseProducts/list-db.spec.js @@ -116,7 +116,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(403, done); }); @@ -127,7 +127,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(404, done); }); @@ -138,7 +138,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(404, done); }); @@ -149,14 +149,14 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); done(); @@ -170,14 +170,14 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); done(); diff --git a/src/routes/phaseProducts/list.js b/src/routes/phaseProducts/list.js index 5899c425..842a0891 100644 --- a/src/routes/phaseProducts/list.js +++ b/src/routes/phaseProducts/list.js @@ -1,14 +1,36 @@ import _ from 'lodash'; -import config from 'config'; import util from '../../util'; +import models from '../../models'; -const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); -const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); +const permissions = require('tc-core-library-js').middleware.permissions; -const eClient = util.getElasticSearchClient(); +const retrieveFromDB = async (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const phaseId = _.parseInt(req.params.phaseId); -const permissions = require('tc-core-library-js').middleware.permissions; + // check if the project and phase are exist + return models.ProjectPhase.findOne({ + where: { id: phaseId, projectId }, + raw: true, + }).then((countPhase) => { + if (!countPhase) { + const apiErr = new Error('project phase not found for project id ' + + `${projectId} and phase id ${phaseId}`); + apiErr.status = 404; + throw apiErr; + } + + const parameters = { + projectId, + phaseId, + }; + + return models.PhaseProduct.search(parameters, req.log) + .then(({ rows }) => res.json(rows)); + }) + .catch(err => next(err)); +}; module.exports = [ // check permission @@ -19,30 +41,37 @@ module.exports = [ const phaseId = _.parseInt(req.params.phaseId); // Get project from ES - eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: req.params.projectId }) - .then((doc) => { - if (!doc) { - const err = new Error(`active project not found for project id ${projectId}`); - err.status = 404; - throw err; + util.fetchByIdFromES('phaseProducts', { + query: { + nested: { + path: 'phases', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'phases.id': phaseId } }, + { term: { 'phases.projectId': projectId } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No phase product found in ES'); + return retrieveFromDB(req, res, next); } - - // Get the phases - let phases = _.isArray(doc._source.phases) ? doc._source.phases : []; // eslint-disable-line no-underscore-dangle - - // Get the phase by id - phases = _.filter(phases, { id: phaseId }); - if (phases.length <= 0) { - const err = new Error(`active project phase not found for phase id ${phaseId}`); - err.status = 404; - throw err; - } - - // Get the products - let products = phases[0].products; - products = _.isArray(products) ? products : []; // eslint-disable-line no-underscore-dangle - - res.json(util.wrapResponse(req.id, products, products.length)); + req.log.debug('phase product found in ES'); + // Get the phases + const phases = data[0].inner_hits.phases.hits.hits[0]._source; // eslint-disable-line no-underscore-dangle + const products = _.isArray(phases.products) ? phases.products : []; + return res.json(products); }) .catch(err => next(err)); }, diff --git a/src/routes/phaseProducts/list.spec.js b/src/routes/phaseProducts/list.spec.js index e670b65e..7a5e39da 100644 --- a/src/routes/phaseProducts/list.spec.js +++ b/src/routes/phaseProducts/list.spec.js @@ -132,7 +132,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(403, done); }); @@ -143,7 +143,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(404, done); }); @@ -154,7 +154,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(404, done); }); @@ -165,14 +165,14 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); done(); @@ -186,14 +186,14 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); done(); diff --git a/src/routes/phaseProducts/update.js b/src/routes/phaseProducts/update.js index e129b316..c1668371 100644 --- a/src/routes/phaseProducts/update.js +++ b/src/routes/phaseProducts/update.js @@ -5,24 +5,22 @@ import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { EVENT } from '../../constants'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = tcMiddleware.permissions; const updatePhaseProductValidation = { - body: { - param: Joi.object().keys({ - name: Joi.string().optional(), - type: Joi.string().optional(), - templateId: Joi.number().optional(), - directProjectId: Joi.number().positive().optional(), - billingAccountId: Joi.number().positive().optional(), - estimatedPrice: Joi.number().positive().optional(), - actualPrice: Joi.number().positive().optional(), - details: Joi.any().optional(), - }).required(), - }, + body: Joi.object().keys({ + name: Joi.string().optional(), + type: Joi.string().optional(), + templateId: Joi.number().optional(), + directProjectId: Joi.number().positive().optional(), + billingAccountId: Joi.number().positive().optional(), + estimatedPrice: Joi.number().positive().optional(), + actualPrice: Joi.number().positive().optional(), + details: Joi.any().optional(), + }).required(), }; @@ -37,7 +35,7 @@ module.exports = [ const phaseId = _.parseInt(req.params.phaseId); const productId = _.parseInt(req.params.productId); - const updatedProps = req.body.param; + const updatedProps = req.body; updatedProps.updatedBy = req.authUser.userId; let previousValue; @@ -74,10 +72,15 @@ module.exports = [ { original: previousValue, updated: updatedValue }, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, - { req, original: previousValue, updated: updatedValue }); - res.json(util.wrapResponse(req.id, updated)); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, + RESOURCES.PHASE_PRODUCT, + _.assign(updatedProps, _.pick(updated, 'id', 'updatedAt'))); + + res.json(updated); }).catch(err => next(err)); }, ]; diff --git a/src/routes/phaseProducts/update.spec.js b/src/routes/phaseProducts/update.spec.js index fabbe819..df8c5676 100644 --- a/src/routes/phaseProducts/update.spec.js +++ b/src/routes/phaseProducts/update.spec.js @@ -7,7 +7,7 @@ import server from '../../app'; import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES } from '../../constants'; const should = chai.should(); @@ -124,7 +124,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(403, done); }); @@ -135,7 +135,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(403, done); }); @@ -146,7 +146,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(404, done); }); @@ -157,7 +157,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(404, done); }); @@ -168,7 +168,7 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(404, done); }); @@ -180,9 +180,7 @@ describe('Phase Products', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - estimatedPrice: -15, - }, + estimatedPrice: -15, }) .expect('Content-Type', /json/) .expect(400, done); @@ -195,14 +193,14 @@ describe('Phase Products', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.be.eql(updateBody.name); resJson.type.should.be.eql(updateBody.type); @@ -231,16 +229,14 @@ describe('Phase Products', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when name updated', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when name updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - name: 'new name', - }, + name: 'new name', }) .expect('Content-Type', /json/) .expect(200) @@ -250,29 +246,24 @@ describe('Phase Products', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ name: 'new name' })).should.be.true; done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when estimatedPrice updated', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when estimatedPrice updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - estimatedPrice: 123, - }, + estimatedPrice: 123, }) .expect('Content-Type', /json/) .expect(200) @@ -282,29 +273,24 @@ describe('Phase Products', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ estimatedPrice: 123 })).should.be.true; done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when actualPrice updated', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when actualPrice updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - actualPrice: 123, - }, + actualPrice: 123, }) .expect('Content-Type', /json/) .expect(200) @@ -314,29 +300,24 @@ describe('Phase Products', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ actualPrice: 123 })).should.be.true; done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when details updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - details: 'something', - }, + details: 'something', }) .expect('Content-Type', /json/) .expect(200) @@ -345,31 +326,25 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PRODUCT_SPECIFICATION_MODIFIED); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ details: 'something' })).should.be.true; done(); }); } }); }); - it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when type updated', (done) => { + it('should not send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when type updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - type: 'another type', - }, + type: 'another type', }) .expect('Content-Type', /json/) .expect(200) @@ -378,7 +353,11 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.notCalled.should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ type: 'another type' })).should.be.true; done(); }); } diff --git a/src/routes/phases/create.js b/src/routes/phases/create.js index 3b1904e9..59aaee7b 100644 --- a/src/routes/phases/create.js +++ b/src/routes/phases/create.js @@ -5,27 +5,25 @@ import Sequelize from 'sequelize'; import models from '../../models'; import util from '../../util'; -import { EVENT } from '../../constants'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = require('tc-core-library-js').middleware.permissions; const addProjectPhaseValidations = { - body: { - param: Joi.object().keys({ - name: Joi.string().required(), - status: Joi.string().required(), - startDate: Joi.date().optional(), - endDate: Joi.date().optional(), - duration: Joi.number().min(0).optional(), - budget: Joi.number().min(0).optional(), - spentBudget: Joi.number().min(0).optional(), - progress: Joi.number().min(0).optional(), - details: Joi.any().optional(), - order: Joi.number().integer().optional(), - productTemplateId: Joi.number().integer().positive().optional(), - }).required(), - }, + body: Joi.object().keys({ + name: Joi.string().required(), + status: Joi.string().required(), + startDate: Joi.date().optional(), + endDate: Joi.date().optional(), + duration: Joi.number().min(0).optional(), + budget: Joi.number().min(0).optional(), + spentBudget: Joi.number().min(0).optional(), + progress: Joi.number().min(0).optional(), + details: Joi.any().optional(), + order: Joi.number().integer().optional(), + productTemplateId: Joi.number().integer().positive().optional(), + }).required(), }; module.exports = [ @@ -35,7 +33,7 @@ module.exports = [ permissions('project.addProjectPhase'), // do the real work (req, res, next) => { - const data = req.body.param; + const data = req.body; // default values const projectId = _.parseInt(req.params.projectId); _.assign(data, { @@ -45,6 +43,7 @@ module.exports = [ }); let newProjectPhase = null; + let otherUpdated = null; models.sequelize.transaction(() => { req.log.debug('Create Phase - Starting transaction'); return models.Project.findOne({ @@ -89,7 +88,22 @@ module.exports = [ }, }); }) - .then(() => { + .then((updatedCount) => { + if (updatedCount) { + return models.ProjectPhase.findAll({ + where: { + projectId, + id: { $ne: newProjectPhase.id }, + order: { $gte: newProjectPhase.order }, + }, + order: [['updatedAt', 'DESC']], + limit: updatedCount[0], + }); + } + return Promise.resolve(); + }) + .then((_otherUpdated) => { + otherUpdated = _otherUpdated || []; if (_.isNil(data.productTemplateId)) { return Promise.resolve(); } @@ -128,10 +142,24 @@ module.exports = [ newProjectPhase, { correlationId: req.id }, ); - req.log.debug('Sending event to Kafka bus for project phase %d', newProjectPhase.id); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, { req, created: newProjectPhase }); - res.status(201).json(util.wrapResponse(req.id, newProjectPhase, 1, 201)); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, + RESOURCES.PHASE, + newProjectPhase); + + // emit the event for other phase order updated + _.map(otherUpdated, phase => + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, + RESOURCES.PHASE, + _.assign(_.pick(phase.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt'))), + ); + + res.status(201).json(newProjectPhase); }) .catch((err) => { util.handleError('Error creating project phase', err, req, next); diff --git a/src/routes/phases/create.spec.js b/src/routes/phases/create.spec.js index 88f1ab3c..693683c9 100644 --- a/src/routes/phases/create.spec.js +++ b/src/routes/phases/create.spec.js @@ -8,7 +8,7 @@ import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; import { - BUS_API_EVENT, + BUS_API_EVENT, RESOURCES, } from '../../constants'; const should = chai.should(); @@ -38,7 +38,6 @@ const validatePhase = (resJson, expectedPhase) => { describe('Project Phases', () => { let projectId; - let projectName; const memberUser = { handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, @@ -71,7 +70,6 @@ describe('Project Phases', () => { lastActivityUserId: '1', }).then((p) => { projectId = p.id; - projectName = p.name; // create members models.ProjectMember.bulkCreate([{ id: 1, @@ -139,7 +137,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(403, done); }); @@ -150,7 +148,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(403, done); }); @@ -163,7 +161,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: reqBody }) + .send(reqBody) .expect('Content-Type', /json/) .expect(400, done); }); @@ -176,7 +174,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: reqBody }) + .send(reqBody) .expect('Content-Type', /json/) .expect(400, done); }); @@ -189,7 +187,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: reqBody }) + .send(reqBody) .expect('Content-Type', /json/) .expect(400, done); }); @@ -202,7 +200,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: reqBody }) + .send(reqBody) .expect('Content-Type', /json/) .expect(400, done); }); @@ -215,7 +213,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: reqBody }) + .send(reqBody) .expect('Content-Type', /json/) .expect(400, done); }); @@ -226,7 +224,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(404, done); }); @@ -237,14 +235,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; validatePhase(resJson, body); done(); } @@ -262,14 +260,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: bodyWithZeros }) + .send(bodyWithZeros) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; validatePhase(resJson, bodyWithZeros); done(); } @@ -282,14 +280,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: _.assign({ order: 1 }, body) }) + .send(_.assign({ order: 1 }, body)) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; validatePhase(resJson, body); resJson.order.should.be.eql(1); @@ -301,11 +299,11 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: _.assign({ order: 1 }, body) }) + .send(_.assign({ order: 1 }, body)) .expect('Content-Type', /json/) .expect(201) .end((err2, res2) => { - const resJson2 = res2.body.result.content; + const resJson2 = res2.body; validatePhase(resJson2, body); resJson2.order.should.be.eql(1); @@ -325,14 +323,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: _.assign({ productTemplateId }, body) }) + .send(_.assign({ productTemplateId }, body)) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; validatePhase(resJson, body); resJson.products.should.have.length(1); @@ -364,13 +362,13 @@ describe('Project Phases', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when phase added', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_ADDED when phase added', (done) => { request(server) .post(`/v5/projects/${projectId}/phases/`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(201) .end((err) => { @@ -379,13 +377,18 @@ describe('Project Phases', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ - projectId, - projectName, - projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, + sinon.match({ resource: RESOURCES.PHASE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, + sinon.match({ name: body.name })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, + sinon.match({ status: body.status })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, + sinon.match({ budget: body.budget })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, + sinon.match({ progress: body.progress })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, + sinon.match({ projectId })).should.be.true; done(); }); } diff --git a/src/routes/phases/delete.js b/src/routes/phases/delete.js index afa440e0..d035cc94 100644 --- a/src/routes/phases/delete.js +++ b/src/routes/phases/delete.js @@ -3,7 +3,8 @@ import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; -import { EVENT } from '../../constants'; +import util from '../../util'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -43,10 +44,15 @@ module.exports = [ deleted, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, { req, deleted }); + + // emit event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, + RESOURCES.PHASE, + _.pick(deleted.toJSON(), 'id')); res.status(204).json({}); }).catch(err => next(err)); }, ]; - diff --git a/src/routes/phases/delete.spec.js b/src/routes/phases/delete.spec.js index 2dfd9a66..7bf7e1e8 100644 --- a/src/routes/phases/delete.spec.js +++ b/src/routes/phases/delete.spec.js @@ -10,8 +10,11 @@ import busApi from '../../services/busApi'; import { BUS_API_EVENT, + RESOURCES, } from '../../constants'; +const should = chai.should(); // eslint-disable-line no-unused-vars + const expectAfterDelete = (projectId, id, err, next) => { if (err) throw err; setTimeout(() => @@ -28,14 +31,8 @@ const expectAfterDelete = (projectId, id, err, next) => { } else { chai.assert.isNotNull(res.deletedAt); chai.assert.isNotNull(res.deletedBy); - - request(server) - .get(`/v5/projects/${projectId}/phases/${id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect(404, next); } + next(); }), 500); }; const body = { @@ -54,7 +51,6 @@ const body = { describe('Project Phases', () => { let projectId; - let projectName; let phaseId; const memberUser = { handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, @@ -87,7 +83,6 @@ describe('Project Phases', () => { lastActivityUserId: '1', }).then((p) => { projectId = p.id; - projectName = p.name; // create members models.ProjectMember.bulkCreate([{ id: 1, @@ -187,7 +182,7 @@ describe('Project Phases', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when phase removed', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_DELETED when phase removed', (done) => { request(server) .delete(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ @@ -199,14 +194,11 @@ describe('Project Phases', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ - projectId, - projectName, - projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, + sinon.match({ resource: RESOURCES.PHASE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, + sinon.match({ id: phaseId })).should.be.true; done(); }); } diff --git a/src/routes/phases/get.js b/src/routes/phases/get.js index cee2c3f2..7ecb77a0 100644 --- a/src/routes/phases/get.js +++ b/src/routes/phases/get.js @@ -11,21 +11,51 @@ module.exports = [ (req, res, next) => { const projectId = _.parseInt(req.params.projectId); const phaseId = _.parseInt(req.params.phaseId); - return models.ProjectPhase - .findOne({ - where: { id: phaseId, projectId }, - raw: true, - }) - .then((phase) => { - if (!phase) { - // handle 404 - const err = new Error('project phase not found for project id ' + - `${projectId} and phase id ${phaseId}`); - err.status = 404; - throw err; - } - res.json(util.wrapResponse(req.id, phase)); - }) - .catch(err => next(err)); + + util.fetchByIdFromES('phases', { + query: { + nested: { + path: 'phases', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'phases.id': phaseId } }, + { term: { 'phases.projectId': projectId } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No phase found in ES'); + return models.ProjectPhase + .findOne({ + where: { id: phaseId, projectId }, + raw: true, + }) + .then((phase) => { + if (!phase) { + // handle 404 + const err = new Error('project phase not found for project id ' + + `${projectId} and phase id ${phaseId}`); + err.status = 404; + throw err; + } + res.json(phase); + }) + .catch(err => next(err)); + } + req.log.debug('phase found in ES'); + return res.json(data[0].inner_hits.phases.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + }) + .catch(next); }, ]; diff --git a/src/routes/phases/get.spec.js b/src/routes/phases/get.spec.js index 3e3410d7..76299814 100644 --- a/src/routes/phases/get.spec.js +++ b/src/routes/phases/get.spec.js @@ -131,7 +131,7 @@ describe('Project Phases', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.be.eql('test project phase'); resJson.status.should.be.eql('active'); @@ -155,7 +155,7 @@ describe('Project Phases', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.be.eql('test project phase'); resJson.status.should.be.eql('active'); diff --git a/src/routes/phases/list-db.js b/src/routes/phases/list-db.js index 60337cb1..c4c052c8 100644 --- a/src/routes/phases/list-db.js +++ b/src/routes/phases/list-db.js @@ -23,8 +23,8 @@ module.exports = [ } // Parse the fields string to determine what fields are to be returned - const rawFields = req.query.fields ? decodeURIComponent(req.query.fields).split(',') : PHASE_ATTRIBUTES; - let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'startDate'; + const rawFields = req.query.fields ? req.query.fields.split(',') : PHASE_ATTRIBUTES; + let sort = req.query.sort ? req.query.sort : 'startDate'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } @@ -53,8 +53,8 @@ module.exports = [ }; try { - const { rows, count } = await models.ProjectPhase.search(parameters, req.log); - return res.json(util.wrapResponse(req.id, rows, count)); + const { rows } = await models.ProjectPhase.search(parameters, req.log); + return res.json(rows); } catch (err) { return next(err); } diff --git a/src/routes/phases/list-db.spec.js b/src/routes/phases/list-db.spec.js index cf1207d8..aa9876bb 100644 --- a/src/routes/phases/list-db.spec.js +++ b/src/routes/phases/list-db.spec.js @@ -98,7 +98,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(403, done); }); @@ -109,7 +109,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(404, done); }); @@ -120,14 +120,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); done(); @@ -141,14 +141,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); done(); diff --git a/src/routes/phases/list.js b/src/routes/phases/list.js index e0084184..fd063a95 100644 --- a/src/routes/phases/list.js +++ b/src/routes/phases/list.js @@ -14,15 +14,14 @@ const PHASE_ATTRIBUTES = _.keys(models.ProjectPhase.rawAttributes); const permissions = tcMiddleware.permissions; - module.exports = [ permissions('project.view'), (req, res, next) => { const projectId = _.parseInt(req.params.projectId); // Parse the fields string to determine what fields are to be returned - let fields = req.query.fields ? decodeURIComponent(req.query.fields).split(',') : PHASE_ATTRIBUTES; - let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'startDate'; + let fields = req.query.fields ? req.query.fields.split(',') : PHASE_ATTRIBUTES; + let sort = req.query.sort ? req.query.sort : 'startDate'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } @@ -40,17 +39,12 @@ module.exports = [ // Get project from ES return eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: req.params.projectId }) .then((doc) => { - if (!doc) { - const err = new Error(`active project not found for project id ${projectId}`); - err.status = 404; - throw err; - } - + req.log.debug('phase found in ES'); // Get the phases let phases = _.isArray(doc._source.phases) ? doc._source.phases : []; // eslint-disable-line no-underscore-dangle // Sort - phases = _.sortBy(phases, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]); + phases = _.orderBy(phases, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]); fields = _.intersection(fields, [...PHASE_ATTRIBUTES, 'products']); if (_.indexOf(fields, 'id') < 0) { @@ -59,8 +53,36 @@ module.exports = [ phases = _.map(phases, phase => _.pick(phase, fields)); - res.json(util.wrapResponse(req.id, phases, phases.length)); + res.json(phases); }) - .catch(err => next(err)); + .catch((err) => { + if (err.status === 404) { + req.log.debug('No phase found in ES'); + // Load the phases + return models.Project.findByPk(projectId) + .then((project) => { + if (!project) { + const apiErr = new Error(`active project not found for project id ${projectId}`); + apiErr.status = 404; + next(apiErr); + } + + // Get the phases + let phases = _.isArray(project.phases) ? project.phases : []; + + // Sort + phases = _.orderBy(phases, [sortColumnAndOrder[0]], [sortColumnAndOrder[1]]); + + fields = _.intersection(fields, [...PHASE_ATTRIBUTES, 'products']); + if (_.indexOf(fields, 'id') < 0) { + fields.push('id'); + } + + // Write to response + return res.json(_.map(phases, p => _.omit(p.toJSON(), ['deletedAt', 'deletedBy']))); + }); + } + return next(err); + }); }, ]; diff --git a/src/routes/phases/list.spec.js b/src/routes/phases/list.spec.js index bcdfb877..1fc977c0 100644 --- a/src/routes/phases/list.spec.js +++ b/src/routes/phases/list.spec.js @@ -114,7 +114,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(403, done); }); @@ -125,7 +125,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(404, done); }); @@ -136,14 +136,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); done(); @@ -157,14 +157,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: body }) + .send(body) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); done(); diff --git a/src/routes/phases/update.js b/src/routes/phases/update.js index 56cb287c..bf85ffb3 100644 --- a/src/routes/phases/update.js +++ b/src/routes/phases/update.js @@ -6,26 +6,24 @@ import Sequelize from 'sequelize'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { EVENT } from '../../constants'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = tcMiddleware.permissions; const updateProjectPhaseValidation = { - body: { - param: Joi.object().keys({ - name: Joi.string().optional(), - status: Joi.string().optional(), - startDate: Joi.date().optional(), - endDate: Joi.date().optional(), - duration: Joi.number().min(0).optional(), - budget: Joi.number().min(0).optional(), - spentBudget: Joi.number().min(0).optional(), - progress: Joi.number().min(0).optional(), - details: Joi.any().optional(), - order: Joi.number().integer().optional(), - }).required(), - }, + body: Joi.object().keys({ + name: Joi.string().optional(), + status: Joi.string().optional(), + startDate: Joi.date().optional(), + endDate: Joi.date().optional(), + duration: Joi.number().min(0).optional(), + budget: Joi.number().min(0).optional(), + spentBudget: Joi.number().min(0).optional(), + progress: Joi.number().min(0).optional(), + details: Joi.any().optional(), + order: Joi.number().integer().optional(), + }).required(), }; @@ -39,7 +37,7 @@ module.exports = [ const projectId = _.parseInt(req.params.projectId); const phaseId = _.parseInt(req.params.phaseId); - const updatedProps = req.body.param; + const updatedProps = req.body; updatedProps.updatedBy = req.authUser.userId; let previousValue; @@ -157,10 +155,15 @@ module.exports = [ { original: previousValue, updated, allPhases }, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, - { req, original: previousValue, updated: _.clone(updated.get({ plain: true })) }); - res.json(util.wrapResponse(req.id, updated)); + // emit event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, + RESOURCES.PHASE, + _.assign(updatedProps, _.pick(updated, 'id', 'updatedAt'))); + + res.json(updated); }) .catch(err => next(err)); }, diff --git a/src/routes/phases/update.spec.js b/src/routes/phases/update.spec.js index a8353732..bb68211b 100644 --- a/src/routes/phases/update.spec.js +++ b/src/routes/phases/update.spec.js @@ -10,6 +10,7 @@ import busApi from '../../services/busApi'; import { BUS_API_EVENT, + RESOURCES, } from '../../constants'; const should = chai.should(); @@ -51,7 +52,6 @@ const validatePhase = (resJson, expectedPhase) => { describe('Project Phases', () => { let projectId; - let projectName; let phaseId; let phaseId2; const memberUser = { @@ -85,7 +85,6 @@ describe('Project Phases', () => { lastActivityUserId: '1', }).then((p) => { projectId = p.id; - projectName = p.name; // create members models.ProjectMember.bulkCreate([{ id: 1, @@ -132,7 +131,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(403, done); }); @@ -143,7 +142,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(403, done); }); @@ -154,7 +153,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(404, done); }); @@ -165,7 +164,7 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(404, done); }); @@ -177,9 +176,7 @@ describe('Project Phases', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - progress: -15, - }, + progress: -15, }) .expect('Content-Type', /json/) .expect(400, done); @@ -192,9 +189,7 @@ describe('Project Phases', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - endDate: '2018-05-13T00:00:00Z', - }, + endDate: '2018-05-13T00:00:00Z', }) .expect('Content-Type', /json/) .expect(400, done); @@ -206,14 +201,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: updateBody }) + .send(updateBody) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; validatePhase(resJson, updateBody); done(); } @@ -231,14 +226,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: bodyWithZeros }) + .send(bodyWithZeros) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; validatePhase(resJson, bodyWithZeros); done(); } @@ -251,14 +246,14 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .send({ param: _.assign({ order: 1 }, updateBody) }) + .send(_.assign({ order: 1 }, updateBody)) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; validatePhase(resJson, updateBody); resJson.order.should.be.eql(1); @@ -289,16 +284,14 @@ describe('Project Phases', () => { sandbox.restore(); }); - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when spentBudget updated', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when startDate updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - spentBudget: 123, - }, + startDate: 123, }) .expect('Content-Type', /json/) .expect(200) @@ -307,54 +300,27 @@ describe('Project Phases', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_PAYMENT); + // createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, + sinon.match({ resource: RESOURCES.PHASE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, + sinon.match({ id: phaseId })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, + sinon.match({ updatedBy: testUtil.userIds.copilot })).should.be.true; done(); }); } }); }); - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when progress updated', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when duration updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - progress: 50, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.callCount.should.be.eql(2); - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_PROGRESS); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_PROGRESS_MODIFIED); - done(); - }); - } - }); - }); - - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - param: { - details: { - text: 'something', - }, - }, + duration: 100, }) .expect('Content-Type', /json/) .expect(200) @@ -364,174 +330,8 @@ describe('Project Phases', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_SCOPE); - done(); - }); - } - }); - }); - - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (completed)', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - param: { - status: 'completed', - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED); - done(); - }); - } - }); - }); - - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when budget updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - param: { - budget: 123, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.notCalled.should.be.true; - done(); - }); - } - }); - }); - - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when startDate updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - param: { - startDate: 123, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ - projectId, - projectName, - projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, - // originalPhase: sinon.match(originalPhase), - // updatedPhase: sinon.match(updatedPhase), - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; - done(); - }); - } - }); - }); - - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when duration updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - param: { - duration: 100, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ - projectId, - projectName, - projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; - done(); - }); - } - }); - }); - - it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when order updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - param: { - order: 100, - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.notCalled.should.be.true; - done(); - }); - } - }); - }); - - it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when endDate updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - param: { - endDate: new Date(), - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.notCalled.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, + sinon.match({ duration: 100 })).should.be.true; done(); }); } diff --git a/src/routes/planConfig/revision/get.js b/src/routes/planConfig/revision/get.js index 08ee9617..a038380d 100644 --- a/src/routes/planConfig/revision/get.js +++ b/src/routes/planConfig/revision/get.js @@ -42,7 +42,7 @@ module.exports = [ inner_hits: {}, }, }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No plan config found in ES'); diff --git a/src/routes/planConfig/version/get.js b/src/routes/planConfig/version/get.js index dcb77840..46fe1c7d 100644 --- a/src/routes/planConfig/version/get.js +++ b/src/routes/planConfig/version/get.js @@ -40,7 +40,7 @@ module.exports = [ }, }, sort: { 'planConfigs.version': 'desc' }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No planConfig found in ES'); diff --git a/src/routes/planConfig/version/getVersion.js b/src/routes/planConfig/version/getVersion.js index a5f10bef..ae2d722e 100644 --- a/src/routes/planConfig/version/getVersion.js +++ b/src/routes/planConfig/version/getVersion.js @@ -40,7 +40,7 @@ module.exports = [ }, }, sort: { 'planConfigs.revision': 'desc' }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No planConfig found in ES'); diff --git a/src/routes/priceConfig/revision/get.js b/src/routes/priceConfig/revision/get.js index 34ff6ad7..acd2da9a 100644 --- a/src/routes/priceConfig/revision/get.js +++ b/src/routes/priceConfig/revision/get.js @@ -42,7 +42,7 @@ module.exports = [ inner_hits: {}, }, }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No priceConfi found in ES'); diff --git a/src/routes/priceConfig/version/get.js b/src/routes/priceConfig/version/get.js index c01348a0..d23b4bae 100644 --- a/src/routes/priceConfig/version/get.js +++ b/src/routes/priceConfig/version/get.js @@ -40,7 +40,7 @@ module.exports = [ }, }, sort: { 'priceConfigs.version': 'desc' }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No priceConfig found in ES'); diff --git a/src/routes/priceConfig/version/getVersion.js b/src/routes/priceConfig/version/getVersion.js index 54c1bad4..c0593fd8 100644 --- a/src/routes/priceConfig/version/getVersion.js +++ b/src/routes/priceConfig/version/getVersion.js @@ -40,7 +40,7 @@ module.exports = [ }, }, sort: { 'priceConfigs.revision': 'desc' }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No priceConfig found in ES'); diff --git a/src/routes/productCategories/get.js b/src/routes/productCategories/get.js index 6d0e19da..8279dbc9 100644 --- a/src/routes/productCategories/get.js +++ b/src/routes/productCategories/get.js @@ -29,7 +29,7 @@ module.exports = [ inner_hits: {}, }, }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No productCategory found in ES'); diff --git a/src/routes/productTemplates/get.js b/src/routes/productTemplates/get.js index c29cc744..96780c2b 100644 --- a/src/routes/productTemplates/get.js +++ b/src/routes/productTemplates/get.js @@ -29,7 +29,7 @@ module.exports = [ inner_hits: {}, }, }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No productTemplate found in ES'); diff --git a/src/routes/productTemplates/list.js b/src/routes/productTemplates/list.js index 3e98ed77..6817fb56 100644 --- a/src/routes/productTemplates/list.js +++ b/src/routes/productTemplates/list.js @@ -12,7 +12,7 @@ module.exports = [ (req, res, next) => { util.fetchFromES('productTemplates') .then((data) => { - const filters = util.parseQueryFilter(req.query.filter); + const filters = req.query; if (!util.isValidFilter(filters, ['productKey'])) { util.handleError('Invalid filters', null, req, next); } diff --git a/src/routes/productTemplates/list.spec.js b/src/routes/productTemplates/list.spec.js index f04fcfed..50d6ff7b 100644 --- a/src/routes/productTemplates/list.spec.js +++ b/src/routes/productTemplates/list.spec.js @@ -1,22 +1,23 @@ /** * Tests for list.js */ -// import chai from 'chai'; + import _ from 'lodash'; import request from 'supertest'; - +import chai from 'chai'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; -// const should = chai.should(); +const should = chai.should(); const validateProductTemplates = (count, resJson, expectedTemplates) => { - resJson.should.have.length(count); + should.exist(resJson); + resJson.length.should.be.eql(count); resJson.forEach((pt, idx) => { - pt.should.have.all.keys('id', 'name', 'productKey', 'category', 'subCategory', 'icon', 'brief', 'details', + pt.should.include.all.keys('id', 'name', 'productKey', 'category', 'subCategory', 'icon', 'brief', 'details', 'aliases', 'template', 'disabled', 'form', 'hidden', 'isAddOn', 'createdBy', 'createdAt', 'updatedBy', 'updatedAt'); - pt.should.not.have.all.keys('deletedAt', 'deletedBy'); + pt.should.not.include.all.keys('deletedAt', 'deletedBy'); pt.name.should.be.eql(expectedTemplates[idx].name); pt.productKey.should.be.eql(expectedTemplates[idx].productKey); pt.category.should.be.eql(expectedTemplates[idx].category); @@ -184,7 +185,7 @@ describe('LIST product templates', () => { it('should return filtered templates', (done) => { request(server) - .get('/v5/projects/metadata/productTemplates?filter=productKey%3DproductKey-2') + .get('/v5/projects/metadata/productTemplates?productKey=productKey-2') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 0385e04c..b9e68fce 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -8,9 +8,7 @@ import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; import { PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, - MANAGER_ROLES, INVITE_STATUS, EVENT, BUS_API_EVENT, USER_ROLE } from '../../constants'; -import { createEvent } from '../../services/busApi'; - + MANAGER_ROLES, INVITE_STATUS, EVENT, RESOURCES, USER_ROLE } from '../../constants'; /** * API to create member invite to project. @@ -19,13 +17,11 @@ import { createEvent } from '../../services/busApi'; const permissions = tcMiddleware.permissions; const addMemberValidations = { - body: { - param: Joi.object().keys({ - userIds: Joi.array().items(Joi.number()).optional().min(1), - emails: Joi.array().items(Joi.string().email()).optional().min(1), - role: Joi.any().valid(_.values(PROJECT_MEMBER_ROLE)).required(), - }).required(), - }, + body: Joi.object().keys({ + userIds: Joi.array().items(Joi.number()).optional().min(1), + emails: Joi.array().items(Joi.string().email()).optional().min(1), + role: Joi.any().valid(_.values(PROJECT_MEMBER_ROLE)).required(), + }).required(), }; /** @@ -124,9 +120,8 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => { return invitePromises; }; -const sendInviteEmail = (req, projectId, invite) => { +const sendInviteEmail = (req, projectId) => { req.log.debug(req.authUser); - const emailEventType = BUS_API_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; const promises = [ models.Project.findOne({ where: { id: projectId }, @@ -136,39 +131,6 @@ const sendInviteEmail = (req, projectId, invite) => { ]; return Promise.all(promises).then((responses) => { req.log.debug(responses); - const project = responses[0]; - const initiator = responses[1] && responses[1].length ? responses[1][0] : { - userId: req.authUser.userId, - firstName: 'Connect', - lastName: 'User', - }; - createEvent(emailEventType, { - data: { - connectURL: config.get('connectUrl'), - accountsAppURL: config.get('accountsAppUrl'), - subject: config.get('inviteEmailSubject'), - projects: [{ - name: project.name, - projectId, - sections: [ - { - EMAIL_INVITES: true, - title: config.get('inviteEmailSectionTitle'), - projectName: project.name, - projectId, - initiator, - }, - ], - }], - }, - recipients: [invite.email], - version: 'v3', - from: { - name: config.get('EMAIL_INVITE_FROM_NAME'), - email: config.get('EMAIL_INVITE_FROM_EMAIL'), - }, - categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], - }, req.log); }).catch((error) => { req.log.error(error); }); @@ -180,7 +142,7 @@ module.exports = [ permissions('projectMemberInvite.create'), (req, res, next) => { let failed = []; - const invite = req.body.param; + const invite = req.body; if (!invite.userIds && !invite.emails) { const err = new Error('Either userIds or emails are required'); @@ -257,16 +219,15 @@ module.exports = [ req.log.debug('Creating invites'); return models.Sequelize.Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed)) - // return Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed)) .then((values) => { values.forEach((v) => { - req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED, { + // emit the event + util.sendResourceToKafkaBus( req, - userId: v.userId, - email: v.email, - status: v.status, - role: v.role, - }); + 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, @@ -274,7 +235,7 @@ module.exports = [ ); // send email invite (async) if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) { - sendInviteEmail(req, projectId, v); + sendInviteEmail(req, projectId); } }); return values; @@ -284,9 +245,9 @@ module.exports = [ .then((values) => { const success = _.assign({}, { success: values }); if (failed.length) { - res.status(403).json(util.wrapResponse(req.id, _.assign({}, success, { failed }), null, 403)); + res.status(403).json(_.assign({}, success, { failed })); } else { - res.status(201).json(util.wrapResponse(req.id, success, null, 201)); + res.status(201).json(success); } }) .catch(err => next(err)); diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index 8fb0a4a6..e57c04c6 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -9,7 +9,7 @@ import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { USER_ROLE, PROJECT_MEMBER_ROLE, INVITE_STATUS, BUS_API_EVENT } from '../../constants'; +import { USER_ROLE, PROJECT_MEMBER_ROLE, INVITE_STATUS, BUS_API_EVENT, RESOURCES } from '../../constants'; const should = chai.should(); @@ -168,11 +168,9 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - userIds: [40051332], - emails: ['hello@world.com'], - role: 'customer', - }, + userIds: [40051332], + emails: ['hello@world.com'], + role: 'customer', }) .expect('Content-Type', /json/) .expect(201) @@ -180,7 +178,12 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - res.body.result.status.should.equal(201); + const resJson = res.body.success[0]; + should.exist(resJson); + resJson.role.should.equal('customer'); + resJson.projectId.should.equal(project1.id); + resJson.email.should.equal('hello@world.com'); + server.services.pubsub.publish.calledWith('project.member.invite.created').should.be.true; done(); } }); @@ -194,9 +197,7 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - role: 'customer', - }, + role: 'customer', }) .expect('Content-Type', /json/) .expect(400) @@ -235,10 +236,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - userIds: [40152855], - role: 'copilot', - }, + userIds: [40152855], + role: 'copilot', }) .expect('Content-Type', /json/) .expect(403) @@ -277,10 +276,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - userIds: [40152855], - role: 'copilot', - }, + userIds: [40152855], + role: 'copilot', }) .expect('Content-Type', /json/) .expect(403) @@ -321,10 +318,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - emails: ['hello@world.com'], - role: 'customer', - }, + emails: ['hello@world.com'], + role: 'customer', }) .expect('Content-Type', /json/) .expect(201) @@ -332,7 +327,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success[0]; + const resJson = res.body.success[0]; should.exist(resJson); resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); @@ -374,10 +369,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - emails: ['hello@world.com'], - role: 'customer', - }, + emails: ['hello@world.com'], + role: 'customer', }) .expect('Content-Type', /json/) .expect(201) @@ -385,7 +378,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success[0]; + const resJson = res.body.success[0]; should.exist(resJson); resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); @@ -423,10 +416,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - userIds: [40152855], - role: 'customer', - }, + userIds: [40152855], + role: 'customer', }) .expect('Content-Type', /json/) .expect(201) @@ -434,7 +425,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success[0]; + const resJson = res.body.success[0]; should.exist(resJson); resJson.role.should.equal('customer'); resJson.projectId.should.equal(project2.id); @@ -471,10 +462,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - userIds: [40051335], - role: 'customer', - }, + userIds: [40051335], + role: 'customer', }) .expect('Content-Type', /json/) .expect(201) @@ -482,7 +471,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success; + const resJson = res.body.success; should.exist(resJson); resJson.length.should.equal(0); server.services.pubsub.publish.neverCalledWith('project.member.invite.created').should.be.true; @@ -527,10 +516,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - userIds: [40152855], - role: 'manager', - }, + userIds: [40152855], + role: 'manager', }) .expect('Content-Type', /json/) .expect(403) @@ -556,15 +543,13 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - userIds: [40152855], - role: 'manager', - }, + userIds: [40152855], + role: 'manager', }) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content.success[0]; + const resJson = res.body.success[0]; should.exist(resJson); resJson.role.should.equal('manager'); resJson.projectId.should.equal(project1.id); @@ -583,15 +568,13 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - userIds: [40152855], - role: 'account_manager', - }, + userIds: [40152855], + role: 'account_manager', }) .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content.success[0]; + const resJson = res.body.success[0]; should.exist(resJson); resJson.role.should.equal('account_manager'); resJson.projectId.should.equal(project1.id); @@ -610,10 +593,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - userIds: [40152855], - role: 'account_manager', - }, + userIds: [40152855], + role: 'account_manager', }) .expect('Content-Type', /json/) .expect(403) @@ -621,9 +602,8 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.failed[0]; + const resJson = res.body.failed[0]; should.exist(resJson); - res.body.result.status.should.equal(403); const errorMessage = _.get(resJson, 'message', ''); sinon.assert.match(errorMessage, /.*cannot be added with a Manager role to the project/); done(); @@ -657,10 +637,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - userIds: [40051331], - role: 'copilot', - }, + userIds: [40051331], + role: 'copilot', }) .expect('Content-Type', /json/) .expect(201) @@ -668,7 +646,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success[0]; + const resJson = res.body.success[0]; should.exist(resJson); resJson.role.should.equal('copilot'); resJson.projectId.should.equal(project1.id); @@ -686,10 +664,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - emails: ['DUPLICATE_LOWERCASE@test.com'], - role: 'customer', - }, + emails: ['DUPLICATE_LOWERCASE@test.com'], + role: 'customer', }) .expect('Content-Type', /json/) .expect(201) @@ -697,7 +673,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success; + const resJson = res.body.success; should.exist(resJson); resJson.length.should.equal(0); done(); @@ -712,10 +688,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - emails: ['duplicate_uppercase@test.com'], - role: 'customer', - }, + emails: ['duplicate_uppercase@test.com'], + role: 'customer', }) .expect('Content-Type', /json/) .expect(201) @@ -723,7 +697,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success; + const resJson = res.body.success; should.exist(resJson); resJson.length.should.equal(0); done(); @@ -739,10 +713,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - emails: ['WITHdot@gmail.com'], - role: 'customer', - }, + emails: ['WITHdot@gmail.com'], + role: 'customer', }) .expect('Content-Type', /json/) .expect(201) @@ -750,7 +722,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success; + const resJson = res.body.success; should.exist(resJson); resJson.length.should.equal(0); done(); @@ -766,10 +738,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - emails: ['WITHOUT.dot@gmail.com'], - role: 'customer', - }, + emails: ['WITHOUT.dot@gmail.com'], + role: 'customer', }) .expect('Content-Type', /json/) .expect(201) @@ -777,7 +747,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success; + const resJson = res.body.success; should.exist(resJson); resJson.length.should.equal(0); done(); @@ -797,7 +767,7 @@ describe('Project Member Invite create', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends single BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED message when userId invite added', (done) => { + it('sends BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED message when userId invite added', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, @@ -821,10 +791,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - userIds: [3], - role: PROJECT_MEMBER_ROLE.CUSTOMER, - }, + userIds: [3], + role: PROJECT_MEMBER_ROLE.CUSTOMER, }) .expect(201) .end((err) => { @@ -833,18 +801,22 @@ describe('Project Member Invite create', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ - projectId: project1.id, - userId: 3, - email: null, - })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, + sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, + sinon.match({ projectId: project1.id })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, + sinon.match({ userId: 3 })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, + sinon.match({ email: null })).should.be.true; done(); }); } }); }); - it('sends single BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED message when email invite added', (done) => { + it('sends BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED message when email invite added', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, @@ -868,10 +840,8 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - emails: ['hello@world.com'], - role: PROJECT_MEMBER_ROLE.CUSTOMER, - }, + emails: ['hello@world.com'], + role: PROJECT_MEMBER_ROLE.CUSTOMER, }) .expect(201) .end((err) => { @@ -879,12 +849,16 @@ describe('Project Member Invite create', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ - projectId: project1.id, - userId: null, - email: 'hello@world.com', - })).should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, + sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, + sinon.match({ projectId: project1.id })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, + sinon.match({ userId: null })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, + sinon.match({ email: 'hello@world.com' })).should.be.true; done(); }); } diff --git a/src/routes/projectMemberInvites/get.js b/src/routes/projectMemberInvites/get.js index 8de0fd5a..c2b8ced5 100644 --- a/src/routes/projectMemberInvites/get.js +++ b/src/routes/projectMemberInvites/get.js @@ -17,19 +17,60 @@ module.exports = [ (req, res, next) => { const projectId = _.parseInt(req.params.projectId); const currentUserId = req.authUser.userId; + const email = req.authUser.email; let invite; - return models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(projectId, req.authUser.email, currentUserId) - .then((_invite) => { - invite = _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 ${req.authUser.email}`); - err.status = 404; - return next(err); - } - return res.json(util.wrapResponse(req.id, invite)); - }); + + util.fetchByIdFromES('invites', { + query: { + nested: { + path: 'invites', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'invites.projectId': projectId } }, + { + bool: { + should: [ + { term: { 'invites.email': email } }, + { term: { 'invites.userId': currentUserId } }, + ], + minimum_number_should_match: 1, + }, + }, + + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }) + .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) => { + invite = _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; + return next(err); + } + return res.json(invite); + }) + .catch(err => next(err)); + } + req.log.debug('project member found in ES'); + return res.json(data[0].inner_hits.invites.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + }) + .catch(next); }, ]; diff --git a/src/routes/projectMemberInvites/get.spec.js b/src/routes/projectMemberInvites/get.spec.js index 9d9afcad..5b04ae5b 100644 --- a/src/routes/projectMemberInvites/get.spec.js +++ b/src/routes/projectMemberInvites/get.spec.js @@ -101,7 +101,7 @@ describe('GET Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); should.exist(resJson.projectId); resJson.userId.should.be.eql(40051331); diff --git a/src/routes/projectMemberInvites/update.js b/src/routes/projectMemberInvites/update.js index e71ddeda..70dd7d78 100644 --- a/src/routes/projectMemberInvites/update.js +++ b/src/routes/projectMemberInvites/update.js @@ -4,7 +4,7 @@ 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, USER_ROLE } from '../../constants'; +import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, INVITE_STATUS, EVENT, RESOURCES, USER_ROLE } from '../../constants'; /** * API to update invite member to project. @@ -13,8 +13,7 @@ import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, INVITE_STATUS, EVENT, USER_ROLE } f const permissions = tcMiddleware.permissions; const updateMemberValidations = { - body: { - param: Joi.object() + body: Joi.object() .keys({ userId: Joi.number().optional(), email: Joi.string() @@ -25,7 +24,6 @@ const updateMemberValidations = { .required(), }) .required(), - }, }; module.exports = [ @@ -33,7 +31,7 @@ module.exports = [ validate(updateMemberValidations), permissions('projectMemberInvite.put'), (req, res, next) => { - const putInvite = req.body.param; + const putInvite = req.body; const projectId = _.parseInt(req.params.projectId); // userId or email should be provided @@ -93,14 +91,13 @@ module.exports = [ status: putInvite.status, }) .then((updatedInvite) => { - req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED, { + // emit the event + util.sendResourceToKafkaBus( req, - userId: updatedInvite.userId, - email: updatedInvite.email, - status: updatedInvite.status, - role: updatedInvite.role, - createdBy: updatedInvite.createdBy, - }); + 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, }); @@ -135,11 +132,11 @@ module.exports = [ }; return util .addUserToProject(req, member) - .then(() => res.json(util.wrapResponse(req.id, updatedInvite))) + .then(() => res.json(updatedInvite)) .catch(err => next(err)); }); } - return res.json(util.wrapResponse(req.id, updatedInvite)); + return res.json(updatedInvite); }); }); }, diff --git a/src/routes/projectMemberInvites/update.spec.js b/src/routes/projectMemberInvites/update.spec.js index 47b83942..4080ab45 100644 --- a/src/routes/projectMemberInvites/update.spec.js +++ b/src/routes/projectMemberInvites/update.spec.js @@ -8,7 +8,7 @@ import server from '../../app'; import util from '../../util'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, USER_ROLE, PROJECT_MEMBER_ROLE, INVITE_STATUS } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, USER_ROLE, PROJECT_MEMBER_ROLE, INVITE_STATUS } from '../../constants'; const should = chai.should(); @@ -103,9 +103,7 @@ describe('Project member invite update', () => { describe('PUT /projects/{id}/members/invite', () => { const body = { - param: { - status: 'accepted', - }, + status: 'accepted', }; let sandbox; @@ -130,10 +128,8 @@ describe('Project member invite update', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - userId: 123, - status: INVITE_STATUS.CANCELED, - }, + userId: 123, + status: INVITE_STATUS.CANCELED, }) .expect('Content-Type', /json/) .expect(404) @@ -149,9 +145,7 @@ describe('Project member invite update', () => { Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - status: INVITE_STATUS.CANCELED, - }, + status: INVITE_STATUS.CANCELED, }) .expect('Content-Type', /json/) .expect(400) @@ -192,10 +186,8 @@ describe('Project member invite update', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - userId: invite2.userId, - status: INVITE_STATUS.CANCELED, - }, + userId: invite2.userId, + status: INVITE_STATUS.CANCELED, }) .expect('Content-Type', /json/) .expect(403) @@ -236,10 +228,8 @@ describe('Project member invite update', () => { Authorization: `Bearer ${testUtil.jwts.member2}`, }) .send({ - param: { - userId: invite2.userId, - status: INVITE_STATUS.CANCELED, - }, + userId: invite2.userId, + status: INVITE_STATUS.CANCELED, }) .expect('Content-Type', /json/) .expect(403) @@ -280,10 +270,8 @@ describe('Project member invite update', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - userId: invite3.userId, - status: INVITE_STATUS.ACCEPTED, - }, + userId: invite3.userId, + status: INVITE_STATUS.ACCEPTED, }) .expect('Content-Type', /json/) .expect(403) @@ -337,10 +325,8 @@ describe('Project member invite update', () => { Authorization: `Bearer ${testUtil.jwts.member}`, }) .send({ - param: { - userId: invite1.userId, - status: INVITE_STATUS.ACCEPTED, - }, + userId: invite1.userId, + status: INVITE_STATUS.ACCEPTED, }) .expect('Content-Type', /json/) .expect(200) @@ -350,12 +336,17 @@ describe('Project member invite update', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({ - projectId: project1.id, - userId: invite1.userId, - status: INVITE_STATUS.ACCEPTED, - email: null, - })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, + sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, + sinon.match({ projectId: project1.id })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, + sinon.match({ userId: invite1.userId })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, + sinon.match({ status: INVITE_STATUS.ACCEPTED })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, + sinon.match({ email: null })).should.be.true; done(); }); } diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index f3a1fc0c..31ba995a 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -3,7 +3,7 @@ import Joi from 'joi'; import validate from 'express-validation'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; -import { INVITE_STATUS, MANAGER_ROLES, PROJECT_MEMBER_ROLE, USER_ROLE } from '../../constants'; +import { INVITE_STATUS, MANAGER_ROLES, PROJECT_MEMBER_ROLE, USER_ROLE, EVENT, RESOURCES } from '../../constants'; import models from '../../models'; /** @@ -14,13 +14,10 @@ import models from '../../models'; const permissions = tcMiddleware.permissions; const createProjectMemberValidations = { - body: { - param: Joi.object() - .keys({ - role: Joi.any() + body: Joi.object().keys({ + role: Joi.any() .valid(PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.COPILOT), - }), - }, + }), }; module.exports = [ @@ -29,8 +26,8 @@ module.exports = [ permissions('project.addMember'), (req, res, next) => { let targetRole; - if (_.get(req, 'body.param.role')) { - targetRole = _.get(req, 'body.param.role'); + if (_.get(req, 'body.role')) { + targetRole = _.get(req, 'body.role'); if (PROJECT_MEMBER_ROLE.MANAGER === targetRole && !util.hasRoles(req, [USER_ROLE.MANAGER])) { @@ -95,14 +92,28 @@ module.exports = [ .then((_invite) => { invite = _invite; if (!invite) { + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, + RESOURCES.PROJECT_MEMBER, + newMember); + return res.status(201) - .json(util.wrapResponse(req.id, newMember, 1, 201)); + .json(newMember); } return invite.update({ status: INVITE_STATUS.ACCEPTED, }) - .then(() => res.status(201) - .json(util.wrapResponse(req.id, newMember, 1, 201))); + .then(() => { + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, + RESOURCES.PROJECT_MEMBER, + newMember); + return res.status(201).json(newMember); + }); }); }); }) diff --git a/src/routes/projectMembers/create.spec.js b/src/routes/projectMembers/create.spec.js index 82a04e5c..21e05f60 100644 --- a/src/routes/projectMembers/create.spec.js +++ b/src/routes/projectMembers/create.spec.js @@ -83,10 +83,8 @@ describe('Project Members create', () => { Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - userIds: [40051332], - role: 'copilot', - }, + userIds: [40051332], + role: 'copilot', }) .expect('Content-Type', /json/) .expect(201) @@ -94,7 +92,7 @@ describe('Project Members create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content.success[0]; + const resJson = res.body.success[0]; should.exist(resJson); resJson.role.should.equal('copilot'); resJson.projectId.should.equal(project1.id); @@ -106,10 +104,8 @@ describe('Project Members create', () => { Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send({ - param: { - userId: 40051332, - status: 'accepted', - }, + userId: 40051332, + status: 'accepted', }) .expect('Content-Type', /json/) .expect(200) @@ -117,7 +113,7 @@ describe('Project Members create', () => { if (err2) { done(err2); } else { - const resJson2 = res2.body.result.content; + const resJson2 = res2.body; should.exist(resJson2); resJson2.role.should.equal('copilot'); resJson2.projectId.should.equal(project1.id); @@ -131,10 +127,8 @@ describe('Project Members create', () => { Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send({ - param: { - userId: 40051332, - status: 'accepted', - }, + userId: 40051332, + status: 'accepted', }) .expect('Content-Type', /json/) .expect(404) @@ -194,7 +188,7 @@ describe('Project Members create', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.role.should.equal('manager'); resJson.isPrimary.should.be.truthy; diff --git a/src/routes/projectMembers/delete.js b/src/routes/projectMembers/delete.js index b58aaa84..1a145488 100644 --- a/src/routes/projectMembers/delete.js +++ b/src/routes/projectMembers/delete.js @@ -3,7 +3,8 @@ import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; -import { EVENT, PROJECT_MEMBER_ROLE } from '../../constants'; +import util from '../../util'; +import { EVENT, RESOURCES, PROJECT_MEMBER_ROLE } from '../../constants'; /** * API to delete a project member. @@ -76,7 +77,13 @@ module.exports = [ pmember, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, { req, member: pmember }); + + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, + RESOURCES.PROJECT_MEMBER, + { id: pmember.id }); res.status(204).json({}); }).catch(err => next(err)); }, diff --git a/src/routes/projectMembers/delete.spec.js b/src/routes/projectMembers/delete.spec.js index 80923a3f..5eddc353 100644 --- a/src/routes/projectMembers/delete.spec.js +++ b/src/routes/projectMembers/delete.spec.js @@ -9,7 +9,7 @@ import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES } from '../../constants'; const should = chai.should(); @@ -98,11 +98,9 @@ describe('Project members delete', () => { Authorization: `Bearer ${testUtil.jwts.member}`, }) .send({ - param: { - userId: 1, - projectId: project1.id, - role: 'customer', - }, + userId: 1, + projectId: project1.id, + role: 'customer', }) .expect(403, done); }); @@ -114,11 +112,9 @@ describe('Project members delete', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - userId: 1, - projectId: project1.id, - role: 'customer', - }, + userId: 1, + projectId: project1.id, + role: 'customer', }) .expect(403, done); }); @@ -332,7 +328,7 @@ describe('Project members delete', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when manager removed', (done) => { + it('sends BUS_API_EVENT.PROJECT_MEMBER_REMOVED message when manager removed', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { post: () => Promise.resolve({ status: 200, @@ -359,22 +355,19 @@ describe('Project members delete', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.MEMBER_LEFT); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ - projectId: project1.id, - projectName: project1.name, - projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, - userId: 40051334, - initiatorUserId: 40051334, - })).should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, + sinon.match({ resource: RESOURCES.PROJECT_MEMBER })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, + sinon.match({ id: member2.id })).should.be.true; done(); }); } }); }); - it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when copilot removed', (done) => { + it('sends BUS_API_EVENT.PROJECT_MEMBER_REMOVED message when copilot removed', (done) => { request(server) .delete(`/v5/projects/${project1.id}/members/${member1.id}`) .set({ @@ -386,15 +379,12 @@ describe('Project members delete', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.MEMBER_REMOVED); - createEventSpy.secondCall.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ - projectId: project1.id, - projectName: project1.name, - projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, - userId: 40051334, - initiatorUserId: 40051334, - })).should.be.true; + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, + sinon.match({ resource: RESOURCES.PROJECT_MEMBER })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, + sinon.match({ id: member1.id })).should.be.true; done(); }); } diff --git a/src/routes/projectMembers/update.js b/src/routes/projectMembers/update.js index d187814b..ae6e5601 100644 --- a/src/routes/projectMembers/update.js +++ b/src/routes/projectMembers/update.js @@ -5,7 +5,7 @@ import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { EVENT, PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, MANAGER_ROLES } from '../../constants'; +import { EVENT, RESOURCES, PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, MANAGER_ROLES } from '../../constants'; /** * API to update a project member. @@ -13,13 +13,11 @@ import { EVENT, PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, MANAGER_ROLES const permissions = tcMiddleware.permissions; const updateProjectMemberValdiations = { - body: { - param: Joi.object().keys({ - isPrimary: Joi.boolean(), - role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER, + body: Joi.object().keys({ + isPrimary: Joi.boolean(), + role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.OBSERVER).required(), - }), - }, + }), }; module.exports = [ @@ -31,7 +29,7 @@ module.exports = [ */ (req, res, next) => { let projectMember; - let updatedProps = req.body.param; + let updatedProps = req.body; const projectId = _.parseInt(req.params.projectId); const memberRecordId = _.parseInt(req.params.id); updatedProps = _.pick(updatedProps, ['isPrimary', 'role']); @@ -130,10 +128,13 @@ module.exports = [ { original: previousValue, updated: projectMember }, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, - { req, original: previousValue, updated: projectMember }); + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, + RESOURCES.PROJECT_MEMBER, + projectMember); req.log.debug('updated project member', projectMember); - res.json(util.wrapResponse(req.id, projectMember)); + res.json(projectMember); }) .catch(err => next(err))); }, diff --git a/src/routes/projectMembers/update.spec.js b/src/routes/projectMembers/update.spec.js index 69916d52..38594b6c 100644 --- a/src/routes/projectMembers/update.spec.js +++ b/src/routes/projectMembers/update.spec.js @@ -8,7 +8,7 @@ import server from '../../app'; import util from '../../util'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES } from '../../constants'; const should = chai.should(); @@ -88,10 +88,8 @@ describe('Project members update', () => { describe('PUT /projects/{id}/members/{id}', () => { const body = { - param: { - role: 'manager', - isPrimary: false, - }, + role: 'manager', + isPrimary: false, }; let sandbox; @@ -118,9 +116,7 @@ describe('Project members update', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ - param: {}, - }) + .send({}) .expect(400, done); }); @@ -131,9 +127,7 @@ describe('Project members update', () => { Authorization: `Bearer ${testUtil.jwts.member}`, }) .send({ - param: { - role: 'wrong', - }, + role: 'wrong', }) .expect(400, done); }); @@ -145,9 +139,7 @@ describe('Project members update', () => { Authorization: `Bearer ${testUtil.jwts.member}`, }) .send({ - param: { - isPrimary: 'wrong', - }, + isPrimary: 'wrong', }) .expect(400, done); }); @@ -194,9 +186,7 @@ describe('Project members update', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - role: 'customer', - }, + role: 'customer', }) .expect('Content-Type', /json/) .expect(200) @@ -204,7 +194,7 @@ describe('Project members update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.role.should.equal('customer'); resJson.isPrimary.should.be.true; @@ -251,9 +241,9 @@ describe('Project members update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); - resJson.role.should.equal(body.param.role); + resJson.role.should.equal(body.role); resJson.isPrimary.should.be.false; resJson.updatedBy.should.equal(40051332); server.services.pubsub.publish.calledWith('project.member.updated').should.be.true; @@ -292,9 +282,9 @@ describe('Project members update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); - resJson.role.should.equal(body.param.role); + resJson.role.should.equal(body.role); resJson.isPrimary.should.be.false; resJson.updatedBy.should.equal(40051332); deleteSpy.should.have.been.calledOnce; @@ -355,10 +345,8 @@ describe('Project members update', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - role: 'manager', - isPrimary: false, - }, + role: 'manager', + isPrimary: false, }) .expect('Content-Type', /json/) .expect(200) @@ -366,7 +354,7 @@ describe('Project members update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.role.should.equal('manager'); resJson.isPrimary.should.be.false; @@ -409,10 +397,8 @@ describe('Project members update', () => { Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send({ - param: { - role: 'manager', - isPrimary: false, - }, + role: 'manager', + isPrimary: false, }) .expect('Content-Type', /json/) .expect(200) @@ -420,7 +406,7 @@ describe('Project members update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.role.should.equal('manager'); resJson.isPrimary.should.be.false; @@ -458,10 +444,8 @@ describe('Project members update', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - role: 'copilot', - isPrimary: true, - }, + role: 'copilot', + isPrimary: true, }) .expect('Content-Type', /json/) .expect(200) @@ -469,7 +453,7 @@ describe('Project members update', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.role.should.equal('copilot'); resJson.isPrimary.should.be.true; @@ -493,7 +477,7 @@ describe('Project members update', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends single BUS_API_EVENT.PROJECT_TEAM_UPDATED message when user role updated', (done) => { + it('sends single BUS_API_EVENT.PROJECT_MEMBER_UPDATED message when user role updated', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, @@ -515,9 +499,7 @@ describe('Project members update', () => { Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - param: { - role: 'customer', - }, + role: 'customer', }) .expect('Content-Type', /json/) .expect(200) @@ -527,13 +509,15 @@ describe('Project members update', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ - projectId: project1.id, - projectName: project1.name, - projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, + sinon.match({ resource: RESOURCES.PROJECT_MEMBER })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, + sinon.match({ id: member2.id })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, + sinon.match({ role: 'customer' })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, + sinon.match({ userId: 40051332 })).should.be.true; done(); }); } diff --git a/src/routes/projectTemplates/get.js b/src/routes/projectTemplates/get.js index 543f0269..593cdd29 100644 --- a/src/routes/projectTemplates/get.js +++ b/src/routes/projectTemplates/get.js @@ -29,7 +29,7 @@ module.exports = [ inner_hits: {}, }, }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No projectTemplate found in ES'); diff --git a/src/routes/projectTypes/get.js b/src/routes/projectTypes/get.js index 2979c559..c203d8f4 100644 --- a/src/routes/projectTypes/get.js +++ b/src/routes/projectTypes/get.js @@ -29,7 +29,7 @@ module.exports = [ inner_hits: {}, }, }, - }) + }, 'metadata') .then((data) => { if (data.length === 0) { req.log.debug('No projectType found in ES'); diff --git a/src/routes/projects/list-db.js b/src/routes/projects/list-db.js index fe8fe940..3511a4ff 100644 --- a/src/routes/projects/list-db.js +++ b/src/routes/projects/list-db.js @@ -1,4 +1,5 @@ import _ from 'lodash'; +import config from 'config'; import Promise from 'bluebird'; import models from '../../models'; import { MANAGER_ROLES } from '../../constants'; @@ -82,7 +83,7 @@ const retrieveProjects = (req, criteria, sort, ffields) => { p.attachments = _.filter(allAttachments, a => a.projectId === p.id); } }); - return { rows, count }; + return { rows, count, pageSize: criteria.limit, page: criteria.page }; }); }); }; @@ -94,9 +95,9 @@ module.exports = [ */ (req, res, next) => { // handle filters - let filters = _.omit(req.query, 'sort', 'perPage', 'page', 'fields', 'limit', 'offset'); + let filters = _.omit(req.query, 'sort', 'perPage', 'page', 'fields'); - let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt'; + let sort = req.query.sort ? req.query.sort : 'createdAt'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } @@ -118,10 +119,12 @@ module.exports = [ const memberOnly = _.get(filters, 'memberOnly', false); filters = _.omit(filters, 'memberOnly'); + const limit = Math.min(req.query.perPage || config.pageSize, config.pageSize); const criteria = { filters, - limit: Math.min(req.query.limit || 20, 20), - offset: req.query.offset || 0, + limit, + offset: ((req.query.page - 1) * limit) || 0, + page: req.query.page || 1, }; req.log.debug(criteria); @@ -130,7 +133,7 @@ module.exports = [ || util.hasRoles(req, MANAGER_ROLES))) { // admins & topcoder managers can see all projects return retrieveProjects(req, criteria, sort, req.query.fields) - .then(result => res.json(result.rows)) + .then(result => util.setPaginationHeaders(req, res, result)) .catch(err => next(err)); } @@ -138,7 +141,7 @@ module.exports = [ criteria.filters.userId = req.authUser.userId; criteria.filters.email = req.authUser.email; return retrieveProjects(req, criteria, sort, req.query.fields) - .then(result => res.json(result.rows)) + .then(result => util.setPaginationHeaders(req, res, result)) .catch(err => next(err)); }, ]; diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index fa83612f..07fc837e 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -489,7 +489,7 @@ const retrieveProjectsFromDB = (req, criteria, sort, ffields) => { p.attachments = _.filter(allAttachments, a => a.projectId === p.id); } }); - return { rows, count }; + return { rows, count, pageSize: criteria.limit, page: criteria.page }; }); }); }; @@ -517,7 +517,7 @@ const retrieveProjects = (req, criteria, sort, ffields) => { const es = util.getElasticSearchClient(); es.search(searchCriteria).then((docs) => { const rows = _.map(docs.hits.hits, single => single._source); // eslint-disable-line no-underscore-dangle - accept({ rows, count: docs.hits.total }); + accept({ rows, count: docs.hits.total, pageSize: criteria.limit, page: criteria.page }); }).catch(reject); }); }; @@ -529,9 +529,9 @@ module.exports = [ */ (req, res, next) => { // handle filters - let filters = _.omit(req.query, 'sort', 'perPage', 'page', 'fields', 'limit', 'offset'); + let filters = _.omit(req.query, 'sort', 'perPage', 'page', 'fields'); - let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt'; + let sort = req.query.sort ? req.query.sort : 'createdAt'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } @@ -554,10 +554,12 @@ module.exports = [ const memberOnly = _.get(filters, 'memberOnly', false); filters = _.omit(filters, 'memberOnly'); + const limit = Math.min(req.query.perPage || config.pageSize, config.pageSize); const criteria = { filters, - limit: Math.min(req.query.limit || 20, 20), - offset: req.query.offset || 0, + limit, + offset: ((req.query.page - 1) * limit) || 0, + page: req.query.page || 1, }; req.log.info(criteria); if (!memberOnly @@ -568,9 +570,10 @@ module.exports = [ .then((result) => { if (result.rows.length === 0) { return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) - .then(r => res.json(r.rows)); + .then(r => util.setPaginationHeaders(req, res, r)); } - return res.json(result.rows); + // set header + return util.setPaginationHeaders(req, res, result); }) .catch(err => next(err)); } @@ -582,9 +585,9 @@ module.exports = [ .then((result) => { if (result.rows.length === 0) { return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) - .then(r => res.json(r.rows)); + .then(r => util.setPaginationHeaders(req, res, r)); } - return res.json(result.rows); + return util.setPaginationHeaders(req, res, result); }) .catch(err => next(err)); }, diff --git a/src/routes/timelines/create.js b/src/routes/timelines/create.js index 44ec10be..cb2e09d0 100644 --- a/src/routes/timelines/create.js +++ b/src/routes/timelines/create.js @@ -9,30 +9,28 @@ import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; import validateTimeline from '../../middlewares/validateTimeline'; import models from '../../models'; -import { EVENT, TIMELINE_REFERENCES, MILESTONE_STATUS, MILESTONE_TEMPLATE_REFERENCES } +import { EVENT, RESOURCES, TIMELINE_REFERENCES, MILESTONE_STATUS, MILESTONE_TEMPLATE_REFERENCES } from '../../constants'; const permissions = tcMiddleware.permissions; const schema = { - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - name: Joi.string().max(255).required(), - description: Joi.string().max(255), - startDate: Joi.date().required(), - endDate: Joi.date().min(Joi.ref('startDate')).allow(null), - reference: Joi.string().valid(_.values(TIMELINE_REFERENCES)).required(), - referenceId: Joi.number().integer().positive().required(), - templateId: Joi.number().integer().min(1).optional(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + id: Joi.any().strip(), + name: Joi.string().max(255).required(), + description: Joi.string().max(255), + startDate: Joi.date().required(), + endDate: Joi.date().min(Joi.ref('startDate')).allow(null), + reference: Joi.string().valid(_.values(TIMELINE_REFERENCES)).required(), + referenceId: Joi.number().integer().positive().required(), + templateId: Joi.number().integer().min(1).optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -42,8 +40,8 @@ module.exports = [ validateTimeline.validateTimelineRequestBody, permissions('timeline.create'), (req, res, next) => { - const templateId = req.body.param.templateId; - const entity = _.assign({}, req.body.param, { + const templateId = req.body.templateId; + const entity = _.assign({}, req.body, { createdBy: req.authUser.userId, updatedBy: req.authUser.userId, }); @@ -119,8 +117,22 @@ module.exports = [ _.assign({ projectId: req.params.projectId }, result), { correlationId: req.id }, ); + + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.TIMELINE_ADDED, + RESOURCES.TIMELINE, + result); + + // emit the event for milestones + _.map(result.milestones, milestone => util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.MILESTONE_ADDED, + RESOURCES.MILESTONE, + milestone)); + // Write to the response - res.status(201).json(util.wrapResponse(req.id, result, 1, 201)); + res.status(201).json(result); return Promise.resolve(); }) .catch(next); diff --git a/src/routes/timelines/create.spec.js b/src/routes/timelines/create.spec.js index 1ac29ed3..6879987f 100644 --- a/src/routes/timelines/create.spec.js +++ b/src/routes/timelines/create.spec.js @@ -191,14 +191,12 @@ describe('CREATE timeline', () => { describe('POST /timelines', () => { const body = { - param: { - name: 'new name', - description: 'new description', - startDate: '2018-05-29T00:00:00.000Z', - endDate: '2018-05-30T00:00:00.000Z', - reference: 'project', - referenceId: 1, - }, + name: 'new name', + description: 'new description', + startDate: '2018-05-29T00:00:00.000Z', + endDate: '2018-05-30T00:00:00.000Z', + reference: 'project', + referenceId: 1, }; it('should return 403 if user is not authenticated', (done) => { @@ -219,12 +217,10 @@ describe('CREATE timeline', () => { }); it('should return 403 for member who is not in the project (timeline refers to a phase)', (done) => { - const bodyWithPhase = { - param: _.assign({}, body.param, { - reference: 'phase', - referenceId: 1, - }), - }; + const bodyWithPhase = _.assign({}, body, { + reference: 'phase', + referenceId: 1, + }); request(server) .post('/v5/timelines') @@ -236,11 +232,9 @@ describe('CREATE timeline', () => { }); it('should return 400 if missing name', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - name: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + name: undefined, + }); request(server) .post('/v5/timelines') @@ -253,11 +247,9 @@ describe('CREATE timeline', () => { }); it('should return 400 if missing startDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + startDate: undefined, + }); request(server) .post('/v5/timelines') @@ -270,12 +262,10 @@ describe('CREATE timeline', () => { }); it('should return 400 if startDate is after endDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: '2018-05-29T00:00:00.000Z', - endDate: '2018-05-28T00:00:00.000Z', - }), - }; + const invalidBody = _.assign({}, body, { + startDate: '2018-05-29T00:00:00.000Z', + endDate: '2018-05-28T00:00:00.000Z', + }); request(server) .post('/v5/timelines') @@ -288,11 +278,9 @@ describe('CREATE timeline', () => { }); it('should return 400 if missing reference', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - reference: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + reference: undefined, + }); request(server) .post('/v5/timelines') @@ -305,11 +293,9 @@ describe('CREATE timeline', () => { }); it('should return 400 if missing referenceId', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - referenceId: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + referenceId: undefined, + }); request(server) .post('/v5/timelines') @@ -322,11 +308,9 @@ describe('CREATE timeline', () => { }); it('should return 400 if invalid reference', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - reference: 'invalid', - }), - }; + const invalidBody = _.assign({}, body, { + reference: 'invalid', + }); request(server) .post('/v5/timelines') @@ -339,11 +323,9 @@ describe('CREATE timeline', () => { }); it('should return 400 if invalid referenceId', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - referenceId: 0, - }), - }; + const invalidBody = _.assign({}, body, { + referenceId: 0, + }); request(server) .post('/v5/timelines') @@ -356,11 +338,9 @@ describe('CREATE timeline', () => { }); it('should return 400 if project does not exist', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - referenceId: 1110, - }), - }; + const invalidBody = _.assign({}, body, { + referenceId: 1110, + }); request(server) .post('/v5/timelines') @@ -373,11 +353,9 @@ describe('CREATE timeline', () => { }); it('should return 400 if project was deleted', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - referenceId: 2, - }), - }; + const invalidBody = _.assign({}, body, { + referenceId: 2, + }); request(server) .post('/v5/timelines') @@ -390,12 +368,10 @@ describe('CREATE timeline', () => { }); it('should return 400 if phase does not exist', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - reference: 'phase', - referenceId: 2222, - }), - }; + const invalidBody = _.assign({}, body, { + reference: 'phase', + referenceId: 2222, + }); request(server) .post('/v5/timelines') @@ -408,12 +384,10 @@ describe('CREATE timeline', () => { }); it('should return 400 if phase was deleted', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - reference: 'phase', - referenceId: 2, - }), - }; + const invalidBody = _.assign({}, body, { + reference: 'phase', + referenceId: 2, + }); request(server) .post('/v5/timelines') @@ -435,14 +409,14 @@ describe('CREATE timeline', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(body.param.name); - resJson.description.should.be.eql(body.param.description); - resJson.startDate.should.be.eql(body.param.startDate); - resJson.endDate.should.be.eql(body.param.endDate); - resJson.reference.should.be.eql(body.param.reference); - resJson.referenceId.should.be.eql(body.param.referenceId); + resJson.name.should.be.eql(body.name); + resJson.description.should.be.eql(body.description); + resJson.startDate.should.be.eql(body.startDate); + resJson.endDate.should.be.eql(body.endDate); + resJson.reference.should.be.eql(body.reference); + resJson.referenceId.should.be.eql(body.referenceId); resJson.createdBy.should.be.eql(40051333); // admin should.exist(resJson.createdAt); @@ -460,7 +434,7 @@ describe('CREATE timeline', () => { it('should return 201 for admin (with milestones)', (done) => { const withMilestones = _.cloneDeep(body); - withMilestones.param.templateId = 1; + withMilestones.templateId = 1; request(server) .post('/v5/timelines') .set({ @@ -470,14 +444,14 @@ describe('CREATE timeline', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(body.param.name); - resJson.description.should.be.eql(body.param.description); - resJson.startDate.should.be.eql(body.param.startDate); - resJson.endDate.should.be.eql(body.param.endDate); - resJson.reference.should.be.eql(body.param.reference); - resJson.referenceId.should.be.eql(body.param.referenceId); + resJson.name.should.be.eql(body.name); + resJson.description.should.be.eql(body.description); + resJson.startDate.should.be.eql(body.startDate); + resJson.endDate.should.be.eql(body.endDate); + resJson.reference.should.be.eql(body.reference); + resJson.referenceId.should.be.eql(body.referenceId); resJson.createdBy.should.be.eql(40051333); // admin should.exist(resJson.createdAt); @@ -550,7 +524,7 @@ describe('CREATE timeline', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051334); // manager resJson.updatedBy.should.be.eql(40051334); // manager done(); @@ -567,7 +541,7 @@ describe('CREATE timeline', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051336); // connect admin resJson.updatedBy.should.be.eql(40051336); // connect admin done(); @@ -584,7 +558,7 @@ describe('CREATE timeline', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051332); // copilot resJson.updatedBy.should.be.eql(40051332); // copilot done(); @@ -601,7 +575,7 @@ describe('CREATE timeline', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051331); // member resJson.updatedBy.should.be.eql(40051331); // member done(); @@ -610,10 +584,8 @@ describe('CREATE timeline', () => { it('should return 201 for member (timeline refers to a phase)', (done) => { const bodyWithPhase = _.merge({}, body, { - param: { - reference: 'phase', - referenceId: 1, - }, + reference: 'phase', + referenceId: 1, }); request(server) .post('/v5/timelines') @@ -624,7 +596,7 @@ describe('CREATE timeline', () => { .expect('Content-Type', /json/) .expect(201) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.createdBy.should.be.eql(40051331); // member resJson.updatedBy.should.be.eql(40051331); // member done(); diff --git a/src/routes/timelines/delete.js b/src/routes/timelines/delete.js index 911291ec..03e5e7d7 100644 --- a/src/routes/timelines/delete.js +++ b/src/routes/timelines/delete.js @@ -6,7 +6,8 @@ import Joi from 'joi'; import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; -import { EVENT } from '../../constants'; +import util from '../../util'; +import { EVENT, RESOURCES } from '../../constants'; import validateTimeline from '../../middlewares/validateTimeline'; const permissions = tcMiddleware.permissions; @@ -33,9 +34,18 @@ module.exports = [ .then(() => timeline.destroy()) // Cascade delete the milestones .then(() => models.Milestone.update({ deletedBy: req.authUser.userId }, { where: { timelineId: timeline.id } })) - .then(() => models.Milestone.destroy({ where: { timelineId: timeline.id } })), + .then(() => models.Milestone.destroy({ where: { timelineId: timeline.id } })) + .then(itemsDeleted => models.Milestone.findAll({ + where: { + timelineId: timeline.id, + }, + attributes: ['id'], + paranoid: false, + order: [['deletedAt', 'DESC']], + limit: itemsDeleted, + })), ) - .then(() => { + .then((milestones) => { // Send event to bus req.log.debug('Sending event to RabbitMQ bus for timeline %d', deleted.id); req.app.services.pubsub.publish(EVENT.ROUTING_KEY.TIMELINE_REMOVED, @@ -43,6 +53,19 @@ module.exports = [ { correlationId: req.id }, ); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.TIMELINE_REMOVED, + RESOURCES.TIMELINE, + { id: req.params.timelineId }); + + // emit the event for milestones + _.map(milestones, milestone => util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.MILESTONE_REMOVED, + RESOURCES.MILESTONE, + milestone.toJSON())); + // Write to response res.status(204).end(); return Promise.resolve(); diff --git a/src/routes/timelines/get.js b/src/routes/timelines/get.js index c02ff4be..d55b5af2 100644 --- a/src/routes/timelines/get.js +++ b/src/routes/timelines/get.js @@ -3,6 +3,7 @@ */ import validate from 'express-validation'; import Joi from 'joi'; +import config from 'config'; import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; @@ -10,6 +11,11 @@ import validateTimeline from '../../middlewares/validateTimeline'; const permissions = tcMiddleware.permissions; +const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName'); +const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType'); + +const eClient = util.getElasticSearchClient(); + const schema = { params: { timelineId: Joi.number().integer().positive().required(), @@ -22,16 +28,30 @@ module.exports = [ // checking by the permissions middleware validateTimeline.validateTimelineIdParam, permissions('timeline.view'), - (req, res) => { - // Load the milestones - req.timeline.getMilestones() - .then((milestones) => { - const timeline = _.omit(req.timeline.toJSON(), ['deletedAt', 'deletedBy']); - timeline.milestones = - _.map(milestones, milestone => _.omit(milestone.toJSON(), ['deletedAt', 'deletedBy'])); + (req, res, next) => { + eClient.get({ index: ES_TIMELINE_INDEX, + type: ES_TIMELINE_TYPE, + id: req.params.timelineId, + }) + .then((doc) => { + req.log.debug('timeline found in ES'); + res.json(doc._source); // eslint-disable-line no-underscore-dangle + }) + .catch((err) => { + if (err.status === 404) { + req.log.debug('No timeline found in ES'); + // Load the milestones + return req.timeline.getMilestones() + .then((milestones) => { + const timeline = _.omit(req.timeline.toJSON(), ['deletedAt', 'deletedBy']); + timeline.milestones = + _.map(milestones, milestone => _.omit(milestone.toJSON(), ['deletedAt', 'deletedBy'])); - // Write to response - res.json(util.wrapResponse(req.id, timeline)); - }); + // Write to response + return res.json(timeline); + }); + } + return next(err); + }); }, ]; diff --git a/src/routes/timelines/get.spec.js b/src/routes/timelines/get.spec.js index 67f5726d..7bec2f82 100644 --- a/src/routes/timelines/get.spec.js +++ b/src/routes/timelines/get.spec.js @@ -246,7 +246,7 @@ describe('GET timeline', () => { }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(1); resJson.name.should.be.eql('name 1'); resJson.description.should.be.eql('description 1'); diff --git a/src/routes/timelines/list.js b/src/routes/timelines/list.js index cf7777e8..d9ce06f4 100644 --- a/src/routes/timelines/list.js +++ b/src/routes/timelines/list.js @@ -4,12 +4,18 @@ import config from 'config'; import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; import util from '../../util'; import validateTimeline from '../../middlewares/validateTimeline'; const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName'); const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType'); +const MILESTONE_ATTRIBUTES = _.without( + _.keys(models.Milestone.rawAttributes), + 'deletedAt', +); + /** * Retrieve timelines from elastic search. * @param {Array} esTerms the elastic search terms @@ -31,15 +37,41 @@ function retrieveTimelines(esTerms) { }); } +/** + * Retrieve timelines from database. + * @param {Object} req the req object + * @param {Object} filters the filter object + * @returns {Array} the timelines + */ +function retrieveTimelinesFromDB(req, filters) { + return models.Timeline.search(filters, req.log) + .then((timelines) => { + const timelineIds = _.map(timelines, 'id'); + + // retrieve milestones + return models.Milestone.findAll({ + attributes: MILESTONE_ATTRIBUTES, + where: { timelineId: { $in: timelineIds } }, + raw: true, + }) + .then((values) => { + _.forEach(timelines, (t) => { + t.milestones = _.filter(values, m => m.timelineId === t.id); // eslint-disable-line no-param-reassign + }); + return timelines; + }); + }); +} + const permissions = tcMiddleware.permissions; module.exports = [ - // Validate and get projectId from the reference/referenceId pair, and set to request params for + // Validate and get projectId from the reference/referenceId pair, and set to request query for // checking by the permissions middleware validateTimeline.validateTimelineQueryFilter, permissions('timeline.view'), (req, res, next) => { - const filter = req.params.filter; + const filter = req.query; // Build the elastic search query const esTerms = [{ @@ -50,7 +82,15 @@ module.exports = [ // Retrieve timelines, as we know the user has access for the provided reference/referenceId part return retrieveTimelines(esTerms) - .then(result => res.json(util.wrapResponse(req.id, result.rows, result.count))) + .then((result) => { + if (result.rows.length === 0) { + req.log.debug('Fetch timeline from db'); + return retrieveTimelinesFromDB(req, filter) + .then(timelines => res.json(timelines)); + } + req.log.debug('timeline found from ES'); + return res.json(result.rows); + }) .catch(err => next(err)); }, ]; diff --git a/src/routes/timelines/list.spec.js b/src/routes/timelines/list.spec.js index b69e0557..5ebe767f 100644 --- a/src/routes/timelines/list.spec.js +++ b/src/routes/timelines/list.spec.js @@ -220,7 +220,7 @@ describe('LIST timelines', () => { it('should return 400 for invalid filter key', (done) => { request(server) - .get('/v5/timelines?filter=invalid%3Dproject') + .get('/v5/timelines?invalid=project') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -230,7 +230,7 @@ describe('LIST timelines', () => { it('should return 400 for invalid reference filter', (done) => { request(server) - .get('/v5/timelines?filter=reference%3Dinvalid%26referenceId%3D1') + .get('/v5/timelines?reference=invalid&referenceId=1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -240,7 +240,7 @@ describe('LIST timelines', () => { it('should return 400 for invalid referenceId filter', (done) => { request(server) - .get('/v5/timelines?filter=reference%3Dinvalid%26referenceId%3D0') + .get('/v5/timelines?reference=invalid&referenceId=0') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) @@ -250,7 +250,7 @@ describe('LIST timelines', () => { it('should return 200 for admin', (done) => { request(server) - .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?reference=project&referenceId=1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) @@ -258,7 +258,7 @@ describe('LIST timelines', () => { .end((err, res) => { const timeline = timelines[0]; - let resJson = res.body.result.content; + let resJson = res.body; resJson.should.have.length(1); resJson = _.sortBy(resJson, o => o.id); resJson[0].id.should.be.eql(1); @@ -285,13 +285,13 @@ describe('LIST timelines', () => { it('should return 200 for connect admin', (done) => { request(server) - .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?reference=project&referenceId=1') .set({ Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(1); done(); @@ -300,13 +300,13 @@ describe('LIST timelines', () => { it('should return 200 for connect manager', (done) => { request(server) - .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?reference=project&referenceId=1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(1); done(); @@ -315,12 +315,12 @@ describe('LIST timelines', () => { it('should return 200 for member', (done) => { request(server) - .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?reference=project&referenceId=1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(1); done(); @@ -329,12 +329,12 @@ describe('LIST timelines', () => { it('should return 200 for copilot', (done) => { request(server) - .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?reference=project&referenceId=1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(1); done(); @@ -343,7 +343,7 @@ describe('LIST timelines', () => { it('should return 403 for member with not accessible project', (done) => { request(server) - .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?reference=project&referenceId=1') .set({ Authorization: `Bearer ${testUtil.jwts.member2}`, }) @@ -352,13 +352,13 @@ describe('LIST timelines', () => { it('should return 200 with reference and referenceId filter', (done) => { request(server) - .get('/v5/timelines?filter=reference%3Dproject%26referenceId%3D1') + .get('/v5/timelines?reference=project&referenceId=1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; resJson.should.have.length(1); done(); diff --git a/src/routes/timelines/update.js b/src/routes/timelines/update.js index bf403148..e4c8fd5a 100644 --- a/src/routes/timelines/update.js +++ b/src/routes/timelines/update.js @@ -8,7 +8,7 @@ import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; import validateTimeline from '../../middlewares/validateTimeline'; -import { EVENT, TIMELINE_REFERENCES } from '../../constants'; +import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -16,23 +16,21 @@ const schema = { params: { timelineId: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - id: Joi.any().strip(), - name: Joi.string().max(255).required(), - description: Joi.string().max(255), - startDate: Joi.date().required(), - endDate: Joi.date().min(Joi.ref('startDate')).allow(null), - reference: Joi.string().valid(_.values(TIMELINE_REFERENCES)).required(), - referenceId: Joi.number().integer().positive().required(), - createdAt: Joi.any().strip(), - updatedAt: Joi.any().strip(), - deletedAt: Joi.any().strip(), - createdBy: Joi.any().strip(), - updatedBy: Joi.any().strip(), - deletedBy: Joi.any().strip(), - }).required(), - }, + body: Joi.object().keys({ + id: Joi.any().strip(), + name: Joi.string().max(255).required(), + description: Joi.string().max(255), + startDate: Joi.date().required(), + endDate: Joi.date().min(Joi.ref('startDate')).allow(null), + reference: Joi.string().valid(_.values(TIMELINE_REFERENCES)).required(), + referenceId: Joi.number().integer().positive().required(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), }; module.exports = [ @@ -43,7 +41,7 @@ module.exports = [ validateTimeline.validateTimelineRequestBody, permissions('timeline.edit'), (req, res, next) => { - const entityToUpdate = _.assign(req.body.param, { + const entityToUpdate = _.assign(req.body, { updatedBy: req.authUser.userId, }); @@ -104,11 +102,15 @@ module.exports = [ { original, updated }, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.TIMELINE_UPDATED, - { req, original, updated }); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.TIMELINE_UPDATED, + RESOURCES.TIMELINE, + _.assign(entityToUpdate, _.pick(updated, 'id', 'updatedAt'))); // Write to response - res.json(util.wrapResponse(req.id, updated)); + res.json(updated); return Promise.resolve(); }) .catch(next); diff --git a/src/routes/timelines/update.spec.js b/src/routes/timelines/update.spec.js index 598c1421..1d08ccc5 100644 --- a/src/routes/timelines/update.spec.js +++ b/src/routes/timelines/update.spec.js @@ -9,7 +9,7 @@ import _ from 'lodash'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; -import { EVENT, BUS_API_EVENT } from '../../constants'; +import { EVENT, BUS_API_EVENT, RESOURCES } from '../../constants'; import busApi from '../../services/busApi'; const should = chai.should(); @@ -192,14 +192,12 @@ describe('UPDATE timeline', () => { describe('PATCH /timelines/{timelineId}', () => { const body = { - param: { - name: 'new name 1', - description: 'new description 1', - startDate: '2018-06-01T00:00:00.000Z', - endDate: '2018-06-02T00:00:00.000Z', - reference: 'project', - referenceId: 1, - }, + name: 'new name 1', + description: 'new description 1', + startDate: '2018-06-01T00:00:00.000Z', + endDate: '2018-06-02T00:00:00.000Z', + reference: 'project', + referenceId: 1, }; it('should return 403 if user is not authenticated', (done) => { @@ -280,11 +278,9 @@ describe('UPDATE timeline', () => { }); it('should return 400 if missing name', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - name: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + name: undefined, + }); request(server) .patch('/v5/timelines/1') @@ -297,11 +293,9 @@ describe('UPDATE timeline', () => { }); it('should return 400 if missing startDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + startDate: undefined, + }); request(server) .patch('/v5/timelines/1') @@ -314,12 +308,10 @@ describe('UPDATE timeline', () => { }); it('should return 400 if startDate is after endDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: '2018-05-29T00:00:00.000Z', - endDate: '2018-05-28T00:00:00.000Z', - }), - }; + const invalidBody = _.assign({}, body, { + startDate: '2018-05-29T00:00:00.000Z', + endDate: '2018-05-28T00:00:00.000Z', + }); request(server) .patch('/v5/timelines/1') @@ -332,11 +324,9 @@ describe('UPDATE timeline', () => { }); it('should return 400 if missing reference', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - reference: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + reference: undefined, + }); request(server) .patch('/v5/timelines/1') @@ -349,11 +339,9 @@ describe('UPDATE timeline', () => { }); it('should return 400 if missing referenceId', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - referenceId: undefined, - }), - }; + const invalidBody = _.assign({}, body, { + referenceId: undefined, + }); request(server) .patch('/v5/timelines/1') @@ -366,11 +354,9 @@ describe('UPDATE timeline', () => { }); it('should return 400 if invalid reference', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - reference: 'invalid', - }), - }; + const invalidBody = _.assign({}, body, { + reference: 'invalid', + }); request(server) .patch('/v5/timelines/1') @@ -383,11 +369,9 @@ describe('UPDATE timeline', () => { }); it('should return 400 if invalid referenceId', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - referenceId: 0, - }), - }; + const invalidBody = _.assign({}, body, { + referenceId: 0, + }); request(server) .patch('/v5/timelines/1') @@ -400,11 +384,9 @@ describe('UPDATE timeline', () => { }); it('should return 400 if project does not exist', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - referenceId: 1110, - }), - }; + const invalidBody = _.assign({}, body, { + referenceId: 1110, + }); request(server) .patch('/v5/timelines/1') @@ -417,11 +399,9 @@ describe('UPDATE timeline', () => { }); it('should return 400 if project was deleted', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - referenceId: 2, - }), - }; + const invalidBody = _.assign({}, body, { + referenceId: 2, + }); request(server) .patch('/v5/timelines/1') @@ -434,12 +414,10 @@ describe('UPDATE timeline', () => { }); it('should return 400 if phase does not exist', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - reference: 'phase', - referenceId: 2222, - }), - }; + const invalidBody = _.assign({}, body, { + reference: 'phase', + referenceId: 2222, + }); request(server) .patch('/v5/timelines/1') @@ -452,12 +430,10 @@ describe('UPDATE timeline', () => { }); it('should return 400 if phase was deleted', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - reference: 'phase', - referenceId: 2, - }), - }; + const invalidBody = _.assign({}, body, { + reference: 'phase', + referenceId: 2, + }); request(server) .patch('/v5/timelines/1') @@ -478,14 +454,14 @@ describe('UPDATE timeline', () => { .send(body) .expect(200) .end((err, res) => { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson.id); - resJson.name.should.be.eql(body.param.name); - resJson.description.should.be.eql(body.param.description); - resJson.startDate.should.be.eql(body.param.startDate); - resJson.endDate.should.be.eql(body.param.endDate); - resJson.reference.should.be.eql(body.param.reference); - resJson.referenceId.should.be.eql(body.param.referenceId); + resJson.name.should.be.eql(body.name); + resJson.description.should.be.eql(body.description); + resJson.startDate.should.be.eql(body.startDate); + resJson.endDate.should.be.eql(body.endDate); + resJson.reference.should.be.eql(body.reference); + resJson.referenceId.should.be.eql(body.referenceId); resJson.createdBy.should.be.eql(1); should.exist(resJson.createdAt); @@ -510,12 +486,10 @@ describe('UPDATE timeline', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ - param: _.assign({}, body.param, { - startDate: '2018-05-15T00:00:00.000Z', - endDate: '2018-05-17T00:00:00.000Z', // no affect to milestones - }), - }) + .send(_.assign({}, body, { + startDate: '2018-05-15T00:00:00.000Z', + endDate: '2018-05-17T00:00:00.000Z', // no affect to milestones + })) .expect(200) .end(() => { setTimeout(() => { @@ -544,12 +518,10 @@ describe('UPDATE timeline', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send({ - param: _.assign({}, body.param, { - startDate: '2018-05-12T00:00:00.000Z', // no affect to milestones - endDate: '2018-05-15T00:00:00.000Z', - }), - }) + .send(_.assign({}, body, { + startDate: '2018-05-12T00:00:00.000Z', // no affect to milestones + endDate: '2018-05-15T00:00:00.000Z', + })) .expect(200) .end(() => { setTimeout(() => { @@ -615,12 +587,10 @@ describe('UPDATE timeline', () => { }); it('should return 200 if changing reference and referenceId', (done) => { - const newBody = { - param: _.assign({}, body.param, { - reference: 'phase', - referenceId: 1, - }), - }; + const newBody = _.assign({}, body, { + reference: 'phase', + referenceId: 1, + }); request(server) .patch('/v5/timelines/1') @@ -651,7 +621,7 @@ describe('UPDATE timeline', () => { // not testing fields separately as startDate is required parameter, // thus TIMELINE_ADJUSTED will be always sent - it('should send message BUS_API_EVENT.TIMELINE_ADJUSTED when timeline updated', (done) => { + it('should send message BUS_API_EVENT.TIMELINE_UPDATED when timeline updated', (done) => { request(server) .patch('/v5/timelines/1') .set({ @@ -665,13 +635,8 @@ describe('UPDATE timeline', () => { } else { testUtil.wait(() => { createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.TIMELINE_ADJUSTED, sinon.match({ - projectId: 1, - projectName: 'test1', - projectUrl: 'https://local.topcoder-dev.com/projects/1', - userId: 40051332, - initiatorUserId: 40051332, - })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.TIMELINE_UPDATED, + sinon.match({ resource: RESOURCES.TIMELINE })).should.be.true; done(); }); } diff --git a/src/services/busApi.js b/src/services/busApi.js index 0197523c..56c5e0f8 100644 --- a/src/services/busApi.js +++ b/src/services/busApi.js @@ -49,7 +49,7 @@ function createEvent(topic, payload, logger) { logger.debug('calling bus-api'); return busClient.post('/bus/events', { topic, - originator: 'tc-project-service', + originator: 'project-api', timestamp: (new Date()).toISOString(), 'mime-type': 'application/json', payload, diff --git a/src/tests/seed.js b/src/tests/seed.js index 8790ad96..ffa5fb0f 100644 --- a/src/tests/seed.js +++ b/src/tests/seed.js @@ -362,6 +362,7 @@ models.sequelize.sync({ force: true }) name: 'template 2', productKey: 'productKey 2', category: 'category', + subCategory: 'category', icon: 'http://example.com/icon1.ico', question: 'question 1', info: 'info 1', @@ -375,6 +376,7 @@ models.sequelize.sync({ force: true }) { name: 'Generic work', productKey: 'generic_work', + subCategory: 'category', category: 'category', icon: 'http://example.com/icon1.ico', question: 'question 1', @@ -404,6 +406,7 @@ models.sequelize.sync({ force: true }) name: 'Website product', productKey: 'website_development', category: 'category', + subCategory: 'category', icon: 'http://example.com/icon1.ico', question: 'question 1', info: 'info 1', @@ -432,6 +435,7 @@ models.sequelize.sync({ force: true }) name: 'Application product', productKey: 'application_development', category: 'category', + subCategory: 'category', icon: 'http://example.com/icon1.ico', brief: 'brief 1', details: 'details 1', diff --git a/src/util.js b/src/util.js index c56de049..d8210cda 100644 --- a/src/util.js +++ b/src/util.js @@ -11,7 +11,6 @@ import _ from 'lodash'; -import querystring from 'querystring'; import config from 'config'; import urlencode from 'urlencode'; import elasticsearch from 'elasticsearch'; @@ -166,26 +165,6 @@ _.assignIn(util, { return fields; }, - /** - * Parse the query filters - * @param {String} fqueryFilter the query filter string - * @return {Object} the parsed array - */ - parseQueryFilter: (fqueryFilter) => { - let queryFilter = querystring.parse(fqueryFilter); - // convert in to array - queryFilter = _.mapValues(queryFilter, (val) => { - if (val.indexOf('in(') > -1) { - return { $in: val.substring(3, val.length - 1).split(',') }; - } - return val; - }); - if (queryFilter.id && queryFilter.id.$in) { - queryFilter.id.$in = _.map(queryFilter.id.$in, _.parseInt); - } - return queryFilter; - }, - /** * Moves file from source to destination * @param {object} req request object @@ -322,12 +301,28 @@ _.assignIn(util, { /** * Return the searched resource from elastic search + * @param resource resource name + * @param query search query + * @param index index to search from * @return {Object} the searched resource */ - fetchFromES: Promise.coroutine(function* () { // eslint-disable-line func-names - const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); - const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); - const data = (yield esClient.search({ index: ES_METADATA_INDEX, type: ES_METADATA_TYPE })); + fetchFromES: Promise.coroutine(function* (resource, query, index) { // eslint-disable-line func-names + let INDEX = config.get('elasticsearchConfig.metadataIndexName'); + let TYPE = config.get('elasticsearchConfig.metadataDocType'); + if (index === 'timeline') { + INDEX = config.get('elasticsearchConfig.timelineIndexName'); + TYPE = config.get('elasticsearchConfig.timelineDocType'); + } else if (index === 'project') { + INDEX = config.get('elasticsearchConfig.indexName'); + TYPE = config.get('elasticsearchConfig.docType'); + } + + const data = query ? (yield esClient.search({ index: INDEX, type: TYPE, body: query })) : + (yield esClient.search({ index: INDEX, type: TYPE })); + if (data.hits.hits.length > 0 && data.hits.hits[0].inner_hits) { + return data.hits.hits[0].inner_hits; + } + return data.hits.hits.length > 0 ? data.hits.hits[0]._source : { // eslint-disable-line no-underscore-dangle productTemplates: [], forms: [], @@ -336,23 +331,38 @@ _.assignIn(util, { priceConfigs: [], projectTypes: [], productCategories: [], - orgConfigs: [] }; + orgConfigs: [], + milestoneTemplates: [], + }; }), /** - * Return the searched resource from elastic search + * Return the searched resource from elastic search PROJECT index + * @param resource resource name + * @param query search query + * @param index index to search from * @return {Array} the searched resource */ - fetchByIdFromES: Promise.coroutine(function* (resource, query) { // eslint-disable-line func-names - const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); - const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); - return (yield esClient.search({ index: ES_METADATA_INDEX, - type: ES_METADATA_TYPE, + fetchByIdFromES: Promise.coroutine(function* (resource, query, index) { // eslint-disable-line func-names + let INDEX = config.get('elasticsearchConfig.indexName'); + let TYPE = config.get('elasticsearchConfig.docType'); + if (index === 'timeline') { + INDEX = config.get('elasticsearchConfig.timelineIndexName'); + TYPE = config.get('elasticsearchConfig.timelineDocType'); + } else if (index === 'metadata') { + INDEX = config.get('elasticsearchConfig.metadataIndexName'); + TYPE = config.get('elasticsearchConfig.metadataDocType'); + } + + return (yield esClient.search({ + index: INDEX, + type: TYPE, _source: false, body: query, })).hits.hits; }), + /** * Retrieve member details from userIds */ @@ -514,6 +524,61 @@ _.assignIn(util, { */ getTopcoderProjectMembers: members => _(members).filter(m => m.role !== PROJECT_MEMBER_ROLE.CUSTOMER), + /** + * Set paginated header and respond with data + * @param {Object} req HTTP request + * @param {Object} res HTTP response + * @param {Object} data Data for which pagination need to be applied + * @return {Array} data rows to be returned + */ + setPaginationHeaders: (req, res, data) => { + const totalPages = Math.ceil(data.count / data.pageSize); + let fullUrl = `${req.protocol}://${req.get('host')}${req.url.replace(`&page=${data.page}`, '')}`; + // URL formatting to add pagination parameters accordingly + if (fullUrl.indexOf('?') === -1) { + fullUrl += '?'; + } else { + fullUrl += '&'; + } + + // Pagination follows github style + if (data.count > 0) { // Set Pagination headers only if there is data to paginate + let link = ''; // Content for Link header + + // Set first and last page in Link header + link += `<${fullUrl}page=1>; rel="first"`; + link += `, <${fullUrl}page=${totalPages}>; rel="last"`; + + // Set Prev-Page only if it's not first page and within page limits + if (data.page > 1 && data.page <= totalPages) { + const prevPage = (data.page - 1); + res.set({ + 'X-Prev-Page': prevPage, + }); + link += `, <${fullUrl}page=${prevPage}>; rel="prev"`; + } + + // Set Next-Page only if it's not Last page and within page limits + if (data.page < totalPages) { + const nextPage = (_.parseInt(data.page) + 1); + res.set({ + 'X-Next-Page': (_.parseInt(data.page) + 1), + }); + link += `, <${fullUrl}page=${nextPage}>; rel="next"`; + } + + res.set({ + 'X-Page': data.page, + 'X-Per-Page': data.pageSize, + 'X-Total': data.count, + 'X-Total-Pages': totalPages, + Link: link, + }); + } + // Return the data after setting pagination headers + res.json(data.rows); + }, + /** * Check if the following model exist * @param {Object} keyInfo key information, it includes version and key From 080c4c45dbf36bdb02c4017f38222776d1110b34 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Fri, 11 Oct 2019 13:35:42 +0800 Subject: [PATCH 05/88] feat: latest update of `v5` branch from another repo "projects-api" - added missing CRUD endpoints for attachments and project members - removed usage of direct API in favour of a separate "legacy-project-processor" - removed DB endpoints for projects, phases and phaseProducts Excluded files: - package.json (changed repo) - .circleci/config.yml (build specific in another repo) --- config/custom-environment-variables.json | 6 +- config/default.json | 8 +- config/test.json | 8 +- docs/Project API.postman_collection.json | 501 +- docs/swagger.yaml | 272 +- package-lock.json | 9534 ---------------------- src/app.js | 2 +- src/events/projectMembers/index.js | 78 +- src/permissions/index.js | 2 + src/routes/attachments/download.js | 2 +- src/routes/attachments/list.js | 65 + src/routes/attachments/list.spec.js | 128 + src/routes/index.js | 15 +- src/routes/phaseProducts/list-db.js | 44 - src/routes/phaseProducts/list-db.spec.js | 188 - src/routes/phases/list-db.js | 62 - src/routes/phases/list-db.spec.js | 159 - src/routes/projectMembers/get.js | 78 + src/routes/projectMembers/get.spec.js | 189 + src/routes/projectMembers/list.js | 89 + src/routes/projectMembers/list.spec.js | 176 + src/routes/projectMembers/update.js | 28 - src/routes/projects/create.js | 17 +- src/routes/projects/create.spec.js | 5 - src/routes/projects/list-db.js | 147 - src/routes/projects/list-db.spec.js | 478 -- src/routes/projects/update.js | 15 - src/routes/projects/update.spec.js | 47 - src/services/directProject.js | 124 - 29 files changed, 999 insertions(+), 11468 deletions(-) delete mode 100644 package-lock.json create mode 100644 src/routes/attachments/list.js create mode 100644 src/routes/attachments/list.spec.js delete mode 100644 src/routes/phaseProducts/list-db.js delete mode 100644 src/routes/phaseProducts/list-db.spec.js delete mode 100644 src/routes/phases/list-db.js delete mode 100644 src/routes/phases/list-db.spec.js create mode 100644 src/routes/projectMembers/get.js create mode 100644 src/routes/projectMembers/get.spec.js create mode 100644 src/routes/projectMembers/list.js create mode 100644 src/routes/projectMembers/list.spec.js delete mode 100644 src/routes/projects/list-db.js delete mode 100644 src/routes/projects/list-db.spec.js delete mode 100644 src/services/directProject.js diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index 48ad4d28..48896676 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -10,9 +10,11 @@ "host": "PROJECTS_ES_URL", "apiVersion": "2.3", "indexName": "PROJECTS_ES_INDEX_NAME", - "docType": "projectV5", + "docType": "PROJECTS_ES_DOC_TYPE", "timelineIndexName": "TIMELINES_ES_INDEX_NAME", - "timelineDocType": "TIMELINES_ES_DOC_TYPE" + "timelineDocType": "TIMELINES_ES_DOC_TYPE", + "metadataIndexName": "METADATA_ES_INDEX_NAME", + "metadataDocType": "METADATA_ES_DOC_TYPE" }, "rabbitmqURL": "RABBITMQ_URL", "pubsubQueueName": "PUBSUB_QUEUE_NAME", diff --git a/config/default.json b/config/default.json index 2e1248fc..4b62d372 100644 --- a/config/default.json +++ b/config/default.json @@ -19,13 +19,13 @@ "projectAttachmentPathSuffix": "attachments", "elasticsearchConfig": { "host": "", - "apiVersion": "7.0", + "apiVersion": "6.8", "indexName": "projects", - "docType": "projectV5", + "docType": "doc", "timelineIndexName": "timelines", - "timelineDocType": "timelineV5", + "timelineDocType": "doc", "metadataIndexName": "metadata", - "metadataDocType": "metadataV5" + "metadataDocType": "doc" }, "connectProjectUrl":"", "dbConfig": { diff --git a/config/test.json b/config/test.json index 976241c7..aadd1ea9 100644 --- a/config/test.json +++ b/config/test.json @@ -6,11 +6,13 @@ "logentriesToken": "", "elasticsearchConfig": { "host": "http://localhost:9200", - "apiVersion": "7.0", + "apiVersion": "6.8", "indexName": "projects_test", - "docType": "projectV5", + "docType": "doc", "timelineIndexName": "timelines_test", - "timelineDocType": "timelineV5" + "timelineDocType": "doc", + "metadataIndexName": "metadata_test", + "metadataDocType": "doc" }, "rabbitmqUrl": "amqp://localhost:5672", "connectProjectsUrl": "https://local.topcoder-dev.com/projects/", diff --git a/docs/Project API.postman_collection.json b/docs/Project API.postman_collection.json index 5dfa7d14..cb1e0c8e 100644 --- a/docs/Project API.postman_collection.json +++ b/docs/Project API.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "a47f8b92-7ae6-4e66-b8db-f5bcbe073ed9", + "_postman_id": "90ff8f9b-5661-4642-8b8b-84aa4b581706", "name": "Project API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -398,6 +398,42 @@ "description": "Delete a project attachment" }, "response": [] + }, + { + "name": "List attachments", + "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}}/attachments", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "attachments" + ] + }, + "description": "Delete a project attachment" + }, + "response": [] } ] }, @@ -829,7 +865,100 @@ "response": [] }, { - "name": "Delete project member", + "name": "List project members", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/members", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "members" + ] + }, + "description": "List all the project with no filter. Default sort and limits are applied." + }, + "response": [] + }, + { + "name": "List project members with roles", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/members?role=customer", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "members" + ], + "query": [ + { + "key": "role", + "value": "customer" + } + ] + }, + "description": "List all the project with no filter. Default sort and limits are applied." + }, + "response": [] + }, + { + "name": "Get project member", + "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}}/members/{{memberId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "members", + "{{memberId}}" + ] + }, + "description": "Delete a project's member" + }, + "response": [] + }, + { + "name": "Delete project member Copy", "request": { "method": "DELETE", "header": [ @@ -1292,211 +1421,6 @@ }, "response": [] }, - { - "name": "List projects DB", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ] - }, - "description": "List all the project with no filter. Default sort and limits are applied." - }, - "response": [] - }, - { - "name": "List projects DB with page and perPage", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db?perPage=1&page=1", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ], - "query": [ - { - "key": "perPage", - "value": "1" - }, - { - "key": "page", - "value": "1" - } - ] - }, - "description": "List all the project with no filter. Limit of 1 and offset of 1 is applied" - }, - "response": [] - }, - { - "name": "List projects DB with type filter", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db?type=generic", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ], - "query": [ - { - "key": "type", - "value": "generic" - } - ] - }, - "description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable" - }, - "response": [] - }, - { - "name": "List projects DB with id filter", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db?id=1&id=2", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ], - "query": [ - { - "key": "id", - "value": "1" - }, - { - "key": "id", - "value": "2" - } - ] - }, - "description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable" - }, - "response": [] - }, - { - "name": "List projects DB with sort applied", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db?sort=type desc", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ], - "query": [ - { - "key": "sort", - "value": "type desc" - } - ] - }, - "description": "List all the project with custom sort and no filter. Default sort and limits are applied. The sort string has to be url encoded. Sort is of type `key asc|desc`" - }, - "response": [] - }, - { - "name": "List projects DB and return specific fields", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db?fields=id,name,description", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ], - "query": [ - { - "key": "fields", - "value": "id,name,description" - } - ] - }, - "description": "List all the project with no filter. Default sort and limits are applied. The fields to return is specified as comma separated list. Only those fields should be returned." - }, - "response": [] - }, - { - "name": "List projects DB with copilot token", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token-copilot-40051332}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ] - } - }, - "response": [] - }, { "name": "List projects", "request": { @@ -2628,140 +2552,6 @@ }, "response": [] }, - { - "name": "List Phase DB", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/db", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "db" - ] - } - }, - "response": [] - }, - { - "name": "List Phase DB with fields", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/db?fields=status,name,budget", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "db" - ], - "query": [ - { - "key": "fields", - "value": "status,name,budget" - } - ] - } - }, - "response": [] - }, - { - "name": "List Phase DB with sort", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/db?sort=status desc", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "db" - ], - "query": [ - { - "key": "sort", - "value": "status desc" - } - ] - } - }, - "response": [] - }, - { - "name": "List Phase DB with sort by order", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/db?sort=order desc", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "db" - ], - "query": [ - { - "key": "sort", - "value": "order desc" - } - ] - } - }, - "response": [] - }, { "name": "List Phase", "request": { @@ -3074,33 +2864,6 @@ }, "response": [] }, - { - "name": "List Phase DB Products", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/db", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "{{phaseId}}", - "products", - "db" - ] - } - }, - "response": [] - }, { "name": "List Phase Products", "request": { @@ -7456,4 +7219,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/docs/swagger.yaml b/docs/swagger.yaml index aedaa356..b653e9d8 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -25,74 +25,6 @@ securityDefinitions: name: Authorization in: header paths: - '/projects/db': - get: - tags: - - project - operationId: findProjectsDB - security: - - Bearer: [] - description: Retrieve projects that match the filter directly from database - responses: - '200': - description: A list of projects - schema: - type: array - items: - $ref: '#/definitions/Project' - headers: - X-Next-Page: - type: integer - description: The index of the next page - X-Page: - type: integer - description: The index of the current page (starting at 1) - X-Per-Page: - type: integer - description: The number of items to list per page - X-Prev-Page: - type: integer - description: The index of the previous page - X-Total: - type: integer - description: The total number of items - X-Total-Pages: - type: integer - description: The total number of pages - Link: - type: string - description: Pagination link header. - '401': - description: Unauthorized - schema: - $ref: '#/definitions/ErrorModel' - '400': - description: Bad request - schema: - $ref: '#/definitions/ErrorModel' - '500': - description: Internal Server Error - schema: - $ref: '#/definitions/ErrorModel' - parameters: - - $ref: '#/parameters/pageParam' - - $ref: '#/parameters/perPageParam' - - $ref: '#/parameters/idQueryParam' - - $ref: '#/parameters/statusQueryParam' - - $ref: '#/parameters/typeQueryParam' - - $ref: '#/parameters/memberOnlyQueryParam' - - $ref: '#/parameters/keywordQueryParam' - - $ref: '#/parameters/nameQueryParam' - - $ref: '#/parameters/codeQueryParam' - - $ref: '#/parameters/customerQueryParam' - - $ref: '#/parameters/managerQueryParam' - - name: sort - required: false - description: > - sort projects by status, name, type, createdAt, updatedAt. Default - is createdAt asc - in: query - type: string '/projects': get: tags: @@ -312,6 +244,30 @@ paths: schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/attachments': + get: + tags: + - project attachments + description: Retrieve project attachments + security: + - Bearer: [] + responses: + '200': + description: list of project attachments + schema: + type: array + items: + $ref: '#/definitions/ProjectAttachment' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/projectIdParam' + operationId: listProjectAttachment post: tags: - project attachments @@ -347,6 +303,37 @@ paths: schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/attachments/{id}': + get: + tags: + - project attachments + description: Download project attachment + security: + - Bearer: [] + responses: + '200': + description: a project attachment + schema: + $ref: '#/definitions/ProjectAttachment' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: Not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/projectIdParam' + - in: path + name: id + required: true + description: The id of attachment to download + type: integer + operationId: downloadProjectAttachment patch: tags: - project attachments @@ -428,6 +415,40 @@ paths: schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/members': + get: + tags: + - project members + description: Retrieve project members + security: + - Bearer: [] + responses: + '200': + description: list of project members + schema: + type: array + items: + $ref: '#/definitions/ProjectMember' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/projectIdParam' + - name: role + required: false + description: > + member role + in: query + type: string + operationId: listProjectMember post: tags: - project members @@ -463,6 +484,36 @@ paths: schema: $ref: '#/definitions/ErrorModel' '/projects/{projectId}/members/{id}': + get: + tags: + - project members + description: Get project member + security: + - Bearer: [] + responses: + '200': + description: a project member + schema: + $ref: '#/definitions/ProjectMember' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: Not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/projectIdParam' + - in: path + name: id + required: true + type: integer + operationId: getProjectMember delete: tags: - project members @@ -542,55 +593,6 @@ paths: required: true schema: $ref: '#/definitions/UpdateProjectMember' - '/projects/{projectId}/phases/db': - parameters: - - $ref: '#/parameters/projectIdParam' - get: - tags: - - phase - operationId: findProjectPhasesDB - security: - - Bearer: [] - description: >- - Retrieve all project phases directly from database. All users who can edit project can access - this endpoint. - parameters: - - name: fields - required: false - type: string - in: query - description: | - Comma separated list of project phase fields to return. - - name: sort - required: false - description: > - sort project phases by startDate, endDate, status, order. Default is - startDate asc - in: query - type: string - responses: - '200': - description: A list of project phases - schema: - type: array - items: - $ref: '#/definitions/ProjectPhase' - '400': - description: Bad request - schema: - $ref: '#/definitions/ErrorModel' - '401': - description: Unauthorized - schema: - $ref: '#/definitions/ErrorModel' - '403': - description: Forbidden - schema: - $ref: '#/definitions/ErrorModel' - '500': - description: Internal Server Error - schema: - $ref: '#/definitions/ErrorModel' '/projects/{projectId}/phases': parameters: - $ref: '#/parameters/projectIdParam' @@ -802,42 +804,6 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' - '/projects/{projectId}/phases/{phaseId}/products/db': - parameters: - - $ref: '#/parameters/projectIdParam' - - $ref: '#/parameters/phaseIdParam' - get: - tags: - - phase product - operationId: findPhaseProductsDB - security: - - Bearer: [] - description: >- - Retrieve all phase products directly from database. All users who can edit project can access - this endpoint. - responses: - '200': - description: A list of phase products - schema: - type: array - items: - $ref: '#/definitions/PhaseProduct' - '400': - description: Bad request - schema: - $ref: '#/definitions/ErrorModel' - '401': - description: Unauthorized - schema: - $ref: '#/definitions/ErrorModel' - '403': - description: Forbidden - schema: - $ref: '#/definitions/ErrorModel' - '500': - description: Internal Server Error - schema: - $ref: '#/definitions/ErrorModel' '/projects/{projectId}/phases/{phaseId}/products': parameters: - $ref: '#/parameters/projectIdParam' diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index bd4af3b9..00000000 --- a/package-lock.json +++ /dev/null @@ -1,9534 +0,0 @@ -{ - "name": "tc-projects-service", - "version": "1.4.2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@segment/loosely-validate-event": { - "version": "1.1.2", - "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" - } - }, - "@types/bluebird": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.0.tgz", - "integrity": "sha1-JjNHCk6r6aR82aRf2yDtX5NAe8o=" - }, - "@types/lodash": { - "version": "4.14.134", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.134.tgz", - "integrity": "sha512-2/O0khFUCFeDlbi7sZ7ZFRCcT812fAeOLm7Ev4KbwASkZ575TDrDcY7YyaoHdTOzKcNbfiwLYZqPmoC4wadrsw==" - }, - "@types/node": { - "version": "12.0.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.7.tgz", - "integrity": "sha512-1YKeT4JitGgE4SOzyB9eMwO0nGVNkNEsm9qlIt1Lqm/tG2QEiSMTD4kS3aO6L+w5SClLVxALmIBESK6Mk5wX0A==" - }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "3.3.0" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "agentkeepalive": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", - "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", - "requires": { - "humanize-ms": "1.2.1" - } - }, - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "amqplib": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.3.tgz", - "integrity": "sha512-ZOdUhMxcF+u62rPI+hMtU1NBXSDFQ3eCJJrenamtdQ7YYwh7RZJHOIM1gonVbZ5PyVdYH4xqBPje9OYqk7fnqw==", - "requires": { - "bitsyntax": "0.1.0", - "bluebird": "3.5.5", - "buffer-more-ints": "1.0.0", - "readable-stream": "1.1.14", - "safe-buffer": "5.1.2", - "url-parse": "1.4.7" - } - }, - "analytics-node": { - "version": "2.4.1", - "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.2", - "commander": "2.20.0", - "crypto-token": "1.0.1", - "debug": "2.6.9", - "lodash": "4.17.11", - "remove-trailing-slash": "0.1.0", - "superagent": "3.8.3", - "superagent-retry": "0.6.0" - } - }, - "ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", - "dev": true, - "requires": { - "string-width": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "dev": true, - "optional": true, - "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" - } - }, - "app-module-path": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-1.1.0.tgz", - "integrity": "sha1-pqxTaEUPIJufW4bpo+Smq2/nUxw=" - }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", - "dev": true, - "requires": { - "default-require-extensions": "1.0.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "1.0.3" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "optional": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", - "dev": true, - "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.13.0" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true, - "optional": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "2.1.2" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "async-listener": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", - "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", - "requires": { - "semver": "5.7.0", - "shimmer": "1.2.1" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "auth0-js": { - "version": "9.10.4", - "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.10.4.tgz", - "integrity": "sha512-FKwwA0E9NDrn/MxCzeftA5wx6YCos9wINhuvXeFHDdx+Lis7ykR46kBXJF4+dYjDdC5QhQ7W0cAp6bBS5gS75Q==", - "requires": { - "base64-js": "1.3.0", - "idtoken-verifier": "1.3.1", - "js-cookie": "2.2.0", - "qs": "6.7.0", - "superagent": "3.8.3", - "url-join": "4.0.0", - "winchan": "0.2.1" - } - }, - "aws-sdk": { - "version": "2.471.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.471.0.tgz", - "integrity": "sha512-c7e1939Nep03xLyN+qV1uzzotxYVqtcj+5x87C1s3qOPSG7aSVIbtJfcu4RUdtsKty5qUef6muJ6ohNMRYrW6g==", - "requires": { - "buffer": "4.9.1", - "events": "1.1.1", - "ieee754": "1.1.8", - "jmespath": "0.15.0", - "querystring": "0.2.0", - "sax": "1.2.1", - "url": "0.10.3", - "uuid": "3.3.2", - "xml2js": "0.4.19" - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "axios": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", - "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", - "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "2.0.3" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "3.1.0" - } - }, - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" - } - } - }, - "babel-cli": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz", - "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", - "dev": true, - "requires": { - "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.0", - "convert-source-map": "1.6.0", - "fs-readdir-recursive": "1.1.0", - "glob": "7.1.4", - "lodash": "4.17.11", - "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": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "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.6.0", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.11", - "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": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - } - } - }, - "babel-eslint": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", - "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "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.11", - "source-map": "0.5.7", - "trim-right": "1.0.1" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true, - "requires": { - "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": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "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.11" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true, - "requires": { - "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": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-helper-regex": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", - "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.11" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true, - "requires": { - "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": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-add-module-exports": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz", - "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=", - "dev": true - }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "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.11" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true, - "requires": { - "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": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", - "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", - "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "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" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", - "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", - "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "dev": true, - "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true, - "requires": { - "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": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "regexpu-core": "2.0.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", - "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", - "dev": true, - "requires": { - "regenerator-transform": "0.10.1" - } - }, - "babel-plugin-transform-runtime": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", - "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "core-js": "2.6.9", - "regenerator-runtime": "0.10.5" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - } - } - }, - "babel-preset-es2015": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", - "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.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": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "6.26.3", - "babel-runtime": "6.26.0", - "core-js": "2.6.9", - "home-or-tmp": "2.0.0", - "lodash": "4.17.11", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-runtime": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz", - "integrity": "sha1-eIuUtvY04luRvWxd9y1GdFevsAA=", - "requires": { - "core-js": "2.6.9" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "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.11" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "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.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.11" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.11", - "to-fast-properties": "1.0.3" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "backoff": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", - "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", - "requires": { - "precond": "0.2.3" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "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.1", - "pascalcase": "0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "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" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "0.14.5" - } - }, - "bin-protocol": { - "version": "3.1.1", - "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.3.2" - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bitsyntax": { - "version": "0.1.0", - "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" - } - }, - "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "1.0.4", - "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "1.6.18" - } - }, - "boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", - "dev": true, - "requires": { - "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": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "color-convert": "1.9.3" - } - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "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.5.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "optional": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.3" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "requires": { - "base64-js": "1.3.0", - "ieee754": "1.1.8", - "isarray": "1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - } - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-more-ints": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", - "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" - }, - "buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" - }, - "bunyan": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", - "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", - "requires": { - "dtrace-provider": "0.8.7", - "moment": "2.24.0", - "mv": "2.1.1", - "safe-json-stringify": "1.2.0" - } - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "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.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chai": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", - "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", - "dev": true, - "requires": { - "assertion-error": "1.1.0", - "deep-eql": "0.1.3", - "type-detect": "1.0.0" - } - }, - "chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", - "dev": true, - "requires": { - "check-error": "1.0.2" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "check-more-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.3.0.tgz", - "integrity": "sha1-uDl8adySo+ZF8YkywEWwnHRBnsQ=", - "dev": true - }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "dev": true, - "optional": true, - "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.3", - "fsevents": "1.2.9", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.2.1" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", - "dev": true - }, - "cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", - "dev": true, - "requires": { - "ansi-regex": "2.1.1", - "d": "1.0.0", - "es5-ext": "0.10.50", - "es6-iterator": "2.0.3", - "memoizee": "0.4.14", - "timers-ext": "0.1.7" - } - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "1.0.1" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" - }, - "cls-bluebird": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", - "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", - "requires": { - "is-bluebird": "1.0.2", - "shimmer": "1.2.1" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "codependency": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/codependency/-/codependency-0.1.4.tgz", - "integrity": "sha1-0XY6tyZL1wyR2WJumIYtN5K/jUo=", - "requires": { - "semver": "5.0.1" - }, - "dependencies": { - "semver": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.1.tgz", - "integrity": "sha1-n7P0AE+QDYPEeWj+QvdYPgWDLMk=" - } - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "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" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colors": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", - "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "component-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.1.tgz", - "integrity": "sha1-ikeQFwAjjk/DIml3EjAibyS0Fak=" - }, - "compressible": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", - "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", - "requires": { - "mime-db": "1.40.0" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "requires": { - "accepts": "1.3.7", - "bytes": "3.0.0", - "compressible": "2.0.17", - "debug": "2.6.9", - "on-headers": "1.0.2", - "safe-buffer": "5.1.2", - "vary": "1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - } - } - }, - "config": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/config/-/config-1.31.0.tgz", - "integrity": "sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA==", - "requires": { - "json5": "1.0.1" - } - }, - "config-chain": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dev": true, - "requires": { - "ini": "1.3.5", - "proto-list": "1.2.4" - } - }, - "configstore": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", - "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", - "dev": true, - "requires": { - "dot-prop": "4.2.0", - "graceful-fs": "4.1.15", - "make-dir": "1.3.0", - "unique-string": "1.0.0", - "write-file-atomic": "2.4.3", - "xdg-basedir": "3.0.0" - } - }, - "connection-parse": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/connection-parse/-/connection-parse-0.0.7.tgz", - "integrity": "sha1-GOcxiqsGppkmc3KxDFIm0locmmk=" - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "continuation-local-storage": { - "version": "3.2.1", - "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.10", - "emitter-listener": "1.1.2" - } - }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "requires": { - "object-assign": "4.1.1", - "vary": "1.1.2" - } - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true, - "requires": { - "capture-stack-trace": "1.0.1" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.7.0", - "shebang-command": "1.2.0", - "which": "1.3.1" - } - }, - "crypto-js": { - "version": "3.1.9-1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", - "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" - }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", - "dev": true - }, - "crypto-token": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto-token/-/crypto-token-1.0.1.tgz", - "integrity": "sha1-J8ZIL687Y8L12hFXf4MENG/nl6U=" - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "0.10.50" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "dev": true, - "requires": { - "type-detect": "0.1.1" - }, - "dependencies": { - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - } - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", - "dev": true, - "requires": { - "strip-bom": "2.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "0.2.1" - } - } - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "1.1.1" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "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" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "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" - } - }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, - "requires": { - "is-obj": "1.0.1" - } - }, - "dottie": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", - "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw==" - }, - "dtrace-provider": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz", - "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", - "optional": true, - "requires": { - "nan": "2.14.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" - } - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "editorconfig": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", - "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", - "dev": true, - "requires": { - "commander": "2.20.0", - "lru-cache": "4.1.5", - "semver": "5.7.0", - "sigmund": "1.0.1" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - } - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "elasticsearch": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-16.1.1.tgz", - "integrity": "sha512-OF2fIjcTPfq/4Tj6k4/SZr2IIlfWlBBQoy/em225mfevYFW1abN3nyXKWldXGV+eWI6LWNqB8lb3hAP4d6Rh/Q==", - "requires": { - "agentkeepalive": "3.5.2", - "chalk": "1.1.3", - "lodash": "4.17.11" - } - }, - "emitter-listener": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", - "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", - "requires": { - "shimmer": "1.2.1" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, - "requires": { - "es-to-primitive": "1.2.0", - "function-bind": "1.1.1", - "has": "1.0.3", - "is-callable": "1.1.4", - "is-regex": "1.0.4", - "object-keys": "1.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, - "requires": { - "is-callable": "1.1.4", - "is-date-object": "1.0.1", - "is-symbol": "1.0.2" - } - }, - "es5-ext": { - "version": "0.10.50", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", - "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", - "dev": true, - "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "next-tick": "1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50", - "es6-symbol": "3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-promise-polyfill": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es6-promise-polyfill/-/es6-promise-polyfill-1.2.0.tgz", - "integrity": "sha1-84kl8jyz4+jObNqP93T867sJDN4=" - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.3", - "esrecurse": "4.2.1", - "estraverse": "4.2.0" - } - }, - "eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "requires": { - "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.2.0", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "glob": "7.1.4", - "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.11", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "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": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "1.0.2" - } - } - } - }, - "eslint-config-airbnb-base": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.3.2.tgz", - "integrity": "sha512-/fhjt/VqzBA2SRsx7ErDtv6Ayf+XLw9LIOqmpBuHFCVwyJo2EtzGWMB9fYRFBoWWQLxmNmCpenNiH0RxyeS41w==", - "dev": true, - "requires": { - "eslint-restricted-globals": "0.1.1" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", - "dev": true, - "requires": { - "debug": "2.6.9", - "resolve": "1.11.1" - } - }, - "eslint-module-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz", - "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", - "dev": true, - "requires": { - "debug": "2.6.9", - "pkg-dir": "2.0.0" - } - }, - "eslint-plugin-import": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.17.3.tgz", - "integrity": "sha512-qeVf/UwXFJbeyLbxuY8RgqDyEKCkqV7YC+E5S5uOjAp4tOc8zj01JP3ucoBM8JcEqd1qRasJSg6LLlisirfy0Q==", - "dev": true, - "requires": { - "array-includes": "3.0.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.0", - "has": "1.0.3", - "lodash": "4.17.11", - "minimatch": "3.0.4", - "read-pkg-up": "2.0.0", - "resolve": "1.11.1" - }, - "dependencies": { - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "eslint-restricted-globals": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", - "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "5.7.3", - "acorn-jsx": "3.0.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "4.2.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "4.2.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50" - } - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "6.0.5", - "get-stream": "4.1.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": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "optional": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "optional": true, - "requires": { - "fill-range": "2.2.4" - } - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { - "accepts": "1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "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", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.5", - "qs": "6.7.0", - "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", - "utils-merge": "1.0.1", - "vary": "1.1.2" - } - }, - "express-list-routes": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/express-list-routes/-/express-list-routes-0.1.4.tgz", - "integrity": "sha1-xlwxw/thnHnAVD97TsToMFbs5hY=", - "requires": { - "colors": "1.3.3", - "lodash": "3.10.1" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - } - } - }, - "express-request-id": { - "version": "1.4.1", - "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" - } - }, - "express-sanitizer": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/express-sanitizer/-/express-sanitizer-1.0.5.tgz", - "integrity": "sha512-48/Tf1DZ7JklRVTcXQLHAxhq4GNJTuHq2jjIYhyTmu0Bw+X06YPDD/e/tdn1QLYk706xw4N8JFxtjslRrDGb8g==", - "requires": { - "sanitizer": "0.1.3", - "underscore": "1.8.3" - } - }, - "express-validation": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/express-validation/-/express-validation-0.6.0.tgz", - "integrity": "sha1-DXf0r8flixIBat7FmzJb7v2dwmg=", - "requires": { - "lodash": "4.17.11" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "optional": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "1.3.4", - "object-assign": "4.1.1" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true, - "optional": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "requires": { - "glob": "7.1.4", - "minimatch": "3.0.4" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } - } - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "optional": true, - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "3.1.1", - "repeat-element": "1.1.3", - "repeat-string": "1.6.1" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "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" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "2.0.3" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", - "dev": true - } - } - }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "requires": { - "circular-json": "0.3.3", - "graceful-fs": "4.1.15", - "rimraf": "2.6.3", - "write": "0.2.1" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "7.1.4" - } - } - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "optional": true, - "requires": { - "for-in": "1.0.2" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.3", - "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.8", - "mime-types": "2.1.24" - } - }, - "formatio": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", - "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", - "dev": true, - "requires": { - "samsam": "1.1.2" - } - }, - "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "4.1.15", - "jsonfile": "4.0.0", - "universalify": "0.1.2" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.14.0", - "node-pre-gyp": "0.12.0" - }, - "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.1", - "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": "4.1.1", - "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.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "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.3", - "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.1", - "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.3", - "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.3.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.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" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "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.0", - "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.6.3", - "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.0", - "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.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "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.0.3", - "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", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, - "requires": { - "is-property": "1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "1.0.2" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "1.0.0" - } - }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "optional": true, - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "optional": true, - "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "2.0.1" - } - }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "dev": true, - "requires": { - "ini": "1.3.5" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dev": true, - "requires": { - "create-error-class": "3.0.2", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "is-redirect": "1.0.0", - "is-retry-allowed": "1.1.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" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - } - } - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", - "dev": true, - "requires": { - "neo-async": "2.6.1", - "optimist": "0.6.1", - "source-map": "0.6.1", - "uglify-js": "3.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "6.10.0", - "har-schema": "2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "hashring": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/hashring/-/hashring-3.2.0.tgz", - "integrity": "sha1-/aTv3oqiLNuX+x0qZeiEAeHBRM4=", - "requires": { - "connection-parse": "0.0.7", - "simple-lru-cache": "0.0.2" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true - }, - "http-aws-es": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/http-aws-es/-/http-aws-es-4.0.0.tgz", - "integrity": "sha512-5OJVj9/JSNOVFgIOnBK+9fwDePd35PF1odskYjp/aqstuurZy1XdmHoDP+wPE5LH9Pe/TasIJyARyH7aJnLh/A==" - }, - "http-errors": { - "version": "1.7.2", - "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", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": "1.5.0", - "toidentifier": "1.0.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.16.1" - } - }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "requires": { - "ms": "2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": "2.1.2" - } - }, - "idtoken-verifier": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.3.1.tgz", - "integrity": "sha512-o0aplv9JqTuHz9jywi3fXXAHUWZ0nEWSjS1qBawLU74C+iqScORwBFXoac2zVoggE1hTaImikE8vALkZQu9I3Q==", - "requires": { - "base64-js": "1.3.0", - "crypto-js": "3.1.9-1", - "es6-promise-polyfill": "1.2.0", - "isomorphic-unfetch": "3.0.0", - "jsbn": "0.1.1", - "url-join": "1.1.0" - }, - "dependencies": { - "url-join": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", - "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=" - } - } - }, - "ieee754": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", - "dev": true - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflection": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "inquirer": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "requires": { - "ansi-escapes": "1.4.0", - "ansi-regex": "2.1.1", - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "cli-width": "2.2.0", - "figures": "1.7.0", - "lodash": "4.17.11", - "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": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "1.4.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "1.13.1" - } - }, - "is-bluebird": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", - "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "1.6.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true, - "optional": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "optional": true, - "requires": { - "is-primitive": "2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-generator-function": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", - "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", - "dev": true, - "requires": { - "global-dirs": "0.1.1", - "is-path-inside": "1.0.1" - } - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", - "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", - "dev": true, - "requires": { - "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.1" - } - }, - "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", - "dev": true - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "1.0.2" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true, - "optional": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true, - "optional": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "1.0.3" - } - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, - "requires": { - "has-symbols": "1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "isemail": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.2.1.tgz", - "integrity": "sha1-A1PT2aYpUQgMJiwqoKQrjqjp4qY=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "optional": true, - "requires": { - "isarray": "1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - } - } - }, - "isomorphic-unfetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.0.0.tgz", - "integrity": "sha512-V0tmJSYfkKokZ5mgl0cmfQMTb7MLHsBMngTkbLY0eXvKqiVRRoZP04Ly+KhKrJfKtzC9E6Pp15Jo+bwh7Vi2XQ==", - "requires": { - "node-fetch": "2.6.0", - "unfetch": "4.1.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul": { - "version": "1.1.0-alpha.1", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-1.1.0-alpha.1.tgz", - "integrity": "sha1-eBeVZWAYohdMX2DzZ+5dNhy1e3c=", - "dev": true, - "requires": { - "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": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "2.6.2", - "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": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "dev": true, - "requires": { - "lodash": "4.17.11" - } - } - } - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "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.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "1.2.1", - "mkdirp": "0.5.1", - "path-parse": "1.0.6", - "supports-color": "3.2.3" - }, - "dependencies": { - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", - "dev": true, - "requires": { - "debug": "3.2.6", - "istanbul-lib-coverage": "1.2.1", - "mkdirp": "0.5.1", - "rimraf": "2.6.3", - "source-map": "0.5.7" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "7.1.4" - } - } - } - }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", - "dev": true, - "requires": { - "handlebars": "4.1.2" - } - }, - "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" - }, - "joi": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/joi/-/joi-8.4.2.tgz", - "integrity": "sha1-vXd0ZY/pkFjYmU7R1LmWJITruFk=", - "requires": { - "hoek": "4.2.1", - "isemail": "2.2.1", - "moment": "2.24.0", - "topo": "2.0.2" - } - }, - "join-component": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz", - "integrity": "sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU=" - }, - "js-beautify": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.0.tgz", - "integrity": "sha512-OMwf/tPDpE/BLlYKqZOhqWsd3/z2N3KOlyn1wsCRGFwViE8LOQTcDtathQvHvZc+q+zWmcNAbwKSC+iJoMaH2Q==", - "dev": true, - "requires": { - "config-chain": "1.1.12", - "editorconfig": "0.15.3", - "glob": "7.1.4", - "mkdirp": "0.5.1", - "nopt": "4.0.1" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "dev": true, - "requires": { - "abbrev": "1.0.9", - "osenv": "0.1.5" - } - } - } - }, - "js-cookie": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.0.tgz", - "integrity": "sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s=" - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "1.0.10", - "esprima": "4.0.1" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "1.2.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "4.1.15" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "jsonwebtoken": { - "version": "8.5.1", - "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.1.1", - "ms": "2.1.2", - "semver": "5.7.0" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "5.1.2" - } - }, - "jwks-rsa": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.5.1.tgz", - "integrity": "sha512-FcH0mrrfS/5CDArzw7y4AIsNJ15SkAb8XTmfEnpUDmEsUf/4KfSQifff7hYEyrZuKnhisn4L9Pme4fb/ZoHKqQ==", - "requires": { - "debug": "2.6.9", - "jsonwebtoken": "8.5.1", - "limiter": "1.1.4", - "lru-memoizer": "1.12.0", - "ms": "2.1.2", - "request": "2.88.0" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "1.4.1", - "safe-buffer": "5.1.2" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - }, - "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", - "dev": true, - "requires": { - "package-json": "4.0.1" - } - }, - "lazy-ass": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.3.0.tgz", - "integrity": "sha1-fQ0U7vPslwLG8wxg6oHxqNP5APs=", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "2.0.0" - } - }, - "le_node": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/le_node/-/le_node-1.8.0.tgz", - "integrity": "sha512-NXzjxBskZ4QawTNwlGdRG05jYU0LhV2nxxmP3x7sRMHyROV0jPdyyikO9at+uYrWX3VFt0Y/am11oKITedx0iw==", - "requires": { - "babel-runtime": "6.6.1", - "codependency": "0.1.4", - "json-stringify-safe": "5.0.1", - "lodash": "4.17.11", - "reconnect-core": "1.3.0", - "semver": "5.1.0" - }, - "dependencies": { - "semver": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", - "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=" - } - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" - } - }, - "libpq": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/libpq/-/libpq-1.8.8.tgz", - "integrity": "sha512-0TVzqkbAZZiM8JJy5sagRyXOkvU9zTBlgGX6YdzuWECobc5F81Tp6uuS+djMZrnB5YN4O/ff52hsvXYBRW2gdQ==", - "requires": { - "bindings": "1.2.1", - "nan": "2.14.0" - }, - "dependencies": { - "bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" - } - } - }, - "limiter": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.4.tgz", - "integrity": "sha512-XCpr5bElgDI65vVgstP8TWjv6/QKWm9GU5UG0Pr5sLQ3QLo8NVKsioe+Jed5/3vFOe3IQuqE7DKwTvKQkjTHvg==" - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.15", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - } - }, - "lock": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/lock/-/lock-0.1.4.tgz", - "integrity": "sha1-/sfervF+fDoKVeHaBCgD4l2RdF0=" - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "2.4.2" - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "lolex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", - "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", - "dev": true - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "3.0.2" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "lru-memoizer": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-1.12.0.tgz", - "integrity": "sha1-7+ZXBsyKnMZT+A8NWm6jitlQ41I=", - "requires": { - "lock": "0.1.4", - "lodash": "4.17.11", - "lru-cache": "4.0.2", - "very-fast-args": "1.1.0" - } - }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dev": true, - "requires": { - "es5-ext": "0.10.50" - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "1.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "1.0.1" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true, - "optional": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "0.1.3", - "mimic-fn": "2.1.0", - "p-is-promise": "2.1.0" - } - }, - "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.50", - "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": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/memwatch-next/-/memwatch-next-0.3.0.tgz", - "integrity": "sha1-IREFD5qQbgqi1ypOwPAInHhyb48=", - "requires": { - "bindings": "1.5.0", - "nan": "2.14.0" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "method-override": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.10.tgz", - "integrity": "sha1-49r41d7hDdLc59SuiNYrvud0drQ=", - "requires": { - "debug": "2.6.9", - "methods": "1.1.2", - "parseurl": "1.3.3", - "vary": "1.1.2" - } - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "optional": true, - "requires": { - "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": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/millisecond/-/millisecond-0.1.2.tgz", - "integrity": "sha1-bMWtOGJByrjniv+WT4cCjuyS2sU=" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "requires": { - "mime-db": "1.40.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, - "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "mocha": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", - "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.2.2", - "yargs-parser": "13.0.0", - "yargs-unparser": "1.5.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "2.1.1" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "3.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "2.2.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" - }, - "moment-timezone": { - "version": "0.5.25", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.25.tgz", - "integrity": "sha512-DgEaTyN/z0HFaVcVbSyVCUU6HeFdnNC3vE4c9cgu2dgMTvjBUBdBzWfasTBmAW45u5OIMeCJtU8yNjM22DHucw==", - "requires": { - "moment": "2.24.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "murmur-hash-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/murmur-hash-js/-/murmur-hash-js-1.0.0.tgz", - "integrity": "sha1-UEEEkmnJZjPIZjhpYLL0KJ515bA=" - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "ncp": "2.0.0", - "rimraf": "2.4.5" - } - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "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.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", - "optional": true - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "nice-simple-logger": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nice-simple-logger/-/nice-simple-logger-1.0.1.tgz", - "integrity": "sha1-D55khSe+e+PkmrdvqMjAmK+VG/Y=", - "requires": { - "lodash": "4.17.11" - } - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "no-kafka": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/no-kafka/-/no-kafka-3.4.3.tgz", - "integrity": "sha512-hYnkg1OWVdaxORdzVvdQ4ueWYpf7IICObPzd24BBiDyVG5219VkUnRxSH9wZmisFb6NpgABzlSIL1pIZaCKmXg==", - "requires": { - "@types/bluebird": "3.5.0", - "@types/lodash": "4.14.134", - "bin-protocol": "3.1.1", - "bluebird": "3.5.5", - "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" - } - }, - "node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "2.0.3", - "semver": "5.7.0" - } - }, - "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" - }, - "nodemon": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", - "integrity": "sha512-/DXLzd/GhiaDXXbGId5BzxP1GlsqtMGM9zTmkWrgXtSqjKmGSbLicM/oAy4FR0YWm14jCHRwnR31AHS2dYFHrg==", - "dev": true, - "requires": { - "chokidar": "2.1.6", - "debug": "3.2.6", - "ignore-by-default": "1.0.1", - "minimatch": "3.0.4", - "pstree.remy": "1.1.7", - "semver": "5.7.0", - "supports-color": "5.5.0", - "touch": "3.1.0", - "undefsafe": "2.0.2", - "update-notifier": "2.5.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "1.1.0" - } - } - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "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.3", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "chokidar": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", - "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", - "dev": true, - "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.3", - "braces": "2.3.2", - "fsevents": "1.2.9", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "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.1.2" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "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": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "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.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "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.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "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-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "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.2", - "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": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1.0.9" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "2.7.1", - "resolve": "1.11.1", - "semver": "5.7.0", - "validate-npm-package-license": "3.0.4" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "1.1.0" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "2.0.1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "1.1.3", - "function-bind": "1.1.1", - "has-symbols": "1.0.0", - "object-keys": "1.1.1" - } - }, - "object.entries": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", - "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", - "dev": true, - "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.13.0", - "function-bind": "1.1.1", - "has": "1.0.3" - } - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.13.0" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "optional": true, - "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "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 - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "1.0.0", - "lcid": "2.0.0", - "mem": "4.3.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "output-file-sync": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", - "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", - "dev": true, - "requires": { - "graceful-fs": "4.1.15", - "mkdirp": "0.5.1", - "object-assign": "4.1.1" - } - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "1.3.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "dev": true, - "requires": { - "got": "6.7.1", - "registry-auth-token": "3.4.0", - "registry-url": "3.1.0", - "semver": "5.7.0" - } - }, - "packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "optional": true, - "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "1.3.2" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "2.3.0" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pg": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.11.0.tgz", - "integrity": "sha512-YO4V7vCmEMGoF390LJaFaohWNKaA2ayoQOEZmiHVcAUF+YsRThpf/TaKCgSvsSE7cDm37Q/Cy3Gz41xiX/XjTw==", - "requires": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "0.1.3", - "pg-pool": "2.0.6", - "pg-types": "2.0.1", - "pgpass": "1.0.2", - "semver": "4.3.2" - }, - "dependencies": { - "semver": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", - "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" - } - } - }, - "pg-connection-string": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", - "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" - }, - "pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" - }, - "pg-native": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pg-native/-/pg-native-3.0.0.tgz", - "integrity": "sha512-qZZyywXJ8O4lbiIN7mn6vXIow1fd3QZFqzRe+uET/SZIXvCa3HBooXQA4ZU8EQX8Ae6SmaYtDGLp5DwU+8vrfg==", - "requires": { - "libpq": "1.8.8", - "pg-types": "1.13.0", - "readable-stream": "1.0.31" - }, - "dependencies": { - "pg-types": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz", - "integrity": "sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ==", - "requires": { - "pg-int8": "1.0.1", - "postgres-array": "1.0.3", - "postgres-bytea": "1.0.0", - "postgres-date": "1.0.4", - "postgres-interval": "1.2.0" - } - }, - "postgres-array": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz", - "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==" - }, - "readable-stream": { - "version": "1.0.31", - "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", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - } - } - }, - "pg-pool": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.6.tgz", - "integrity": "sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g==" - }, - "pg-types": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.0.1.tgz", - "integrity": "sha512-b7y6QM1VF5nOeX9ukMQ0h8a9z89mojrBHXfJeSug4mhL0YpxNBm83ot2TROyoAmX/ZOX3UbwVO4EbH7i1ZZNiw==", - "requires": { - "pg-int8": "1.0.1", - "postgres-array": "2.0.0", - "postgres-bytea": "1.0.0", - "postgres-date": "1.0.4", - "postgres-interval": "1.2.0" - } - }, - "pgpass": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", - "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", - "requires": { - "split": "1.0.1" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "2.1.0" - } - }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" - }, - "postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" - }, - "postgres-date": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", - "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==" - }, - "postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "requires": { - "xtend": "4.0.1" - } - }, - "precond": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", - "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true, - "optional": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true - }, - "protocol-buffers-schema": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.3.2.tgz", - "integrity": "sha512-Xdayp8sB/mU+sUV4G7ws8xtYMGdQnxbeIfLjyO9TZZRJdztBGhlmbI5x1qcY4TG5hBkIKGnc28i7nXxaugu88w==" - }, - "proxy-addr": { - "version": "2.0.5", - "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", - "ipaddr.js": "1.9.0" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "psl": { - "version": "1.1.32", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", - "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==" - }, - "pstree.remy": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", - "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" - } - }, - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, - "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "optional": true, - "requires": { - "is-number": "4.0.0", - "kind-of": "6.0.2", - "math-random": "1.0.4" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "optional": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true, - "optional": true - } - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.5.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "4.1.15", - "micromatch": "3.1.10", - "readable-stream": "2.3.6" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "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.3", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "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": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "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.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "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.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "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-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "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.2", - "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": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - } - } - }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "mute-stream": "0.0.5" - } - }, - "really-need": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/really-need/-/really-need-1.9.2.tgz", - "integrity": "sha1-Amp+uwUJ9T89/afJP+uO9TFM3TE=", - "dev": true, - "requires": { - "check-more-types": "2.3.0", - "lazy-ass": "1.3.0" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "1.11.1" - } - }, - "reconnect-core": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reconnect-core/-/reconnect-core-1.3.0.tgz", - "integrity": "sha1-+65SkZp4d9hE4yRtAaLyZwHIM8g=", - "requires": { - "backoff": "2.5.0" - } - }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regenerator-transform": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "private": "0.1.8" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "optional": true, - "requires": { - "is-equal-shallow": "0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" - } - }, - "regexpu-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true, - "requires": { - "regenerate": "1.4.0", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - }, - "registry-auth-token": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", - "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", - "dev": true, - "requires": { - "rc": "1.2.8", - "safe-buffer": "5.1.2" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true, - "requires": { - "rc": "1.2.8" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "remove-trailing-slash": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.0.tgz", - "integrity": "sha1-FJjl3wmEwn5Jt26/Boh8otARUNI=" - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "1.0.2" - } - }, - "request": { - "version": "2.88.0", - "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.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.24", - "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": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" - } - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", - "dev": true, - "requires": { - "path-parse": "1.0.6" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "retry-as-promised": { - "version": "3.2.0", - "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" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "optional": true, - "requires": { - "glob": "6.0.4" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "requires": { - "once": "1.4.0" - } - }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-json-stringify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "0.1.15" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "samsam": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", - "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", - "dev": true - }, - "sanitizer": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/sanitizer/-/sanitizer-0.1.3.tgz", - "integrity": "sha1-1PCvdHXZp7ryqeWmEXGLqheKOeE=" - }, - "sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" - }, - "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "dev": true, - "requires": { - "semver": "5.7.0" - } - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "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", - "fresh": "0.5.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" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "sequelize": { - "version": "5.8.7", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.8.7.tgz", - "integrity": "sha512-1rubZM8fAyCt5ipyS+3HJ3Jbmb8WesLdPJ3jIbTD+78EbuPZILFEA5fK0mliVRBx7oM7oPULeVX0lxSRXBV1jw==", - "requires": { - "bluebird": "3.5.5", - "cls-bluebird": "2.1.0", - "debug": "4.1.1", - "dottie": "2.0.1", - "inflection": "1.12.0", - "lodash": "4.17.11", - "moment": "2.24.0", - "moment-timezone": "0.5.25", - "retry-as-promised": "3.2.0", - "semver": "5.7.0", - "sequelize-pool": "1.0.2", - "toposort-class": "1.0.1", - "uuid": "3.3.2", - "validator": "10.11.0", - "wkx": "0.4.7" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "sequelize-cli": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.5.0.tgz", - "integrity": "sha512-twVQ02alCpr2XvxNmpi32C48WZs6xHTH1OFTfTS5Meg3BVqOM8ghiZoml4FITFjlD8sAJSQjlAHTwqTbuolA6Q==", - "dev": true, - "requires": { - "bluebird": "3.5.5", - "cli-color": "1.4.0", - "fs-extra": "7.0.1", - "js-beautify": "1.10.0", - "lodash": "4.17.11", - "resolve": "1.11.1", - "umzug": "2.2.0", - "yargs": "13.2.2" - } - }, - "sequelize-pool": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-1.0.2.tgz", - "integrity": "sha512-VMKl/gCCdIvB1gFZ7p+oqLFEyZEz3oMMYjkKvfEC7GoO9bBcxmfOOU9RdkoltfXGgBZFigSChihRly2gKtsh2w==", - "requires": { - "bluebird": "3.5.5" - } - }, - "serve-static": { - "version": "1.14.1", - "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", - "send": "0.17.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "dev": true, - "requires": { - "glob": "7.1.4", - "interpret": "1.2.0", - "rechoir": "0.6.2" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } - } - }, - "shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "simple-lru-cache": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz", - "integrity": "sha1-1ZzDoZPBpdAyD4Tucy9uRxPlEd0=" - }, - "sinon": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", - "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", - "dev": true, - "requires": { - "formatio": "1.1.1", - "lolex": "1.3.2", - "samsam": "1.1.2", - "util": "0.12.0" - } - }, - "sinon-chai": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.14.0.tgz", - "integrity": "sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "sleep": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/sleep/-/sleep-5.2.4.tgz", - "integrity": "sha512-SoltvxayTifWOgOGD6CTh+djcp5TaOa/zdbaA38wEH1ahF2azmiLOh8CPt6ExHf0pAJAsA9OCHTS7zK24Ym4yA==", - "dev": true, - "requires": { - "nan": "2.14.0" - } - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "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.2", - "use": "3.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "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" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "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" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.4" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "2.2.0", - "spdx-license-ids": "3.0.4" - } - }, - "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", - "dev": true - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2.3.8" - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "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" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "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.1.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "requires": { - "component-emitter": "1.3.0", - "cookiejar": "2.1.2", - "debug": "3.2.6", - "extend": "3.0.2", - "form-data": "2.3.3", - "formidable": "1.2.1", - "methods": "1.1.2", - "mime": "1.6.0", - "qs": "6.7.0", - "readable-stream": "2.3.6" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "2.1.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "5.1.2" - } - } - } - }, - "superagent-retry": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/superagent-retry/-/superagent-retry-0.6.0.tgz", - "integrity": "sha1-5Js1ypbA47HQ4/SWBRNt8OCgKLc=" - }, - "supertest": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", - "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", - "dev": true, - "requires": { - "methods": "1.1.2", - "superagent": "3.8.3" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "swagger-ui-dist": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.22.3.tgz", - "integrity": "sha512-tmjAsqT43pqg5UoiQ2805c+juX0ASSoI/Ash/0c19jjAOFtTfE93ZrzmFd9hjqVgre935CYeXT0uaku42Lu8xg==" - }, - "swagger-ui-express": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.0.6.tgz", - "integrity": "sha512-7YkBfVWRYjvnGITs7vygM8VNF91iUwX8ReHQaTD9I7q8Ky8SYXZ81gyFc+kovE34iSbCC1yW+n2oII3b3uSxXQ==", - "requires": { - "swagger-ui-dist": "3.22.3" - } - }, - "table": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.11", - "slice-ansi": "0.0.4", - "string-width": "2.1.1" - }, - "dependencies": { - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "tc-core-library-js": { - "version": "github:appirio-tech/tc-core-library-js#f45352974dafe5a10c86fc50bdd59ef399b50c65", - "requires": { - "auth0-js": "9.10.4", - "axios": "0.19.0", - "bunyan": "1.8.12", - "jsonwebtoken": "8.5.1", - "jwks-rsa": "1.5.1", - "le_node": "1.8.0", - "lodash": "4.17.11", - "millisecond": "0.1.2", - "request": "2.88.0" - } - }, - "term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "dev": true, - "requires": { - "execa": "0.7.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "4.0.2", - "shebang-command": "1.2.0", - "which": "1.3.1" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "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" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true - }, - "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dev": true, - "requires": { - "es5-ext": "0.10.50", - "next-tick": "1.0.0" - } - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "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" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - } - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "topo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", - "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", - "requires": { - "hoek": "4.2.1" - } - }, - "toposort-class": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" - }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "1.0.10" - }, - "dependencies": { - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1.0.9" - } - } - } - }, - "tough-cookie": { - "version": "2.4.3", - "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.32", - "punycode": "1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } - } - }, - "traverse": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", - "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "1.1.2" - } - }, - "type-detect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", - "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", - "dev": true, - "optional": true, - "requires": { - "commander": "2.20.0", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "umzug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.2.0.tgz", - "integrity": "sha512-xZLW76ax70pND9bx3wqwb8zqkFGzZIK8dIHD9WdNy/CrNfjWcwQgQkGCuUqcuwEBvUm+g07z+qWvY+pxDmMEEw==", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "bluebird": "3.5.5" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.6.9", - "regenerator-runtime": "0.11.1" - } - } - } - }, - "undefsafe": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", - "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", - "dev": true, - "requires": { - "debug": "2.6.9" - } - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, - "unfetch": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz", - "integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==" - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" - } - } - } - }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "dev": true, - "requires": { - "crypto-random-string": "1.0.0" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "dev": true - }, - "upath": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", - "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", - "dev": true - }, - "update-notifier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", - "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", - "dev": true, - "requires": { - "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": { - "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==", - "dev": true, - "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==", - "dev": true, - "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=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - } - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "2.1.1" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", - "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "url-join": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", - "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=" - }, - "url-parse": { - "version": "1.4.7", - "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" - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "1.0.4" - } - }, - "urlencode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/urlencode/-/urlencode-1.1.0.tgz", - "integrity": "sha1-HyuibwE8hfATP3o61v8nMK33y7c=", - "requires": { - "iconv-lite": "0.4.24" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "user-home": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", - "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", - "dev": true - }, - "util": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.0.tgz", - "integrity": "sha512-pPSOFl7VLhZ7LO/SFABPraZEEurkJUWSMn3MuA/r3WQZc+Z1fqou2JqLSOZbCLl73EUIxuUVX8X4jkX2vfJeAA==", - "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" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "v8flags": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", - "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", - "dev": true, - "requires": { - "user-home": "1.1.1" - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "3.1.0", - "spdx-expression-parse": "3.0.0" - } - }, - "validator": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", - "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - } - }, - "very-fast-args": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/very-fast-args/-/very-fast-args-1.1.0.tgz", - "integrity": "sha1-4W0dH6+KbllqJGQh/ZCneWPQs5Y=" - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "1.0.2" - } - }, - "widest-line": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", - "dev": true, - "requires": { - "string-width": "2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "winchan": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.2.1.tgz", - "integrity": "sha512-QrG9q+ObfmZBxScv0HSCqFm/owcgyR5Sgpiy1NlCZPpFXhbsmNHhTiLWoogItdBUi0fnU7Io/5ABEqRta5/6Dw==" - }, - "wkx": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.7.tgz", - "integrity": "sha512-pHf546L96TK8RradLt1cWaIffstgv/zXZ14CGz5KnBs1AxBX0wm+IDphjJw0qrEqRv8P9W9CdTt8Z1unMRZ19A==", - "requires": { - "@types/node": "12.0.7" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "0.5.1" - } - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "4.1.15", - "imurmurhash": "0.1.4", - "signal-exit": "3.0.2" - } - }, - "wrr-pool": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/wrr-pool/-/wrr-pool-1.1.4.tgz", - "integrity": "sha512-+lEdj42HlYqmzhvkZrx6xEymj0wzPBxqr7U1Xh9IWikMzOge03JSQT9YzTGq54SkOh/noViq32UejADZVzrgAg==", - "requires": { - "lodash": "4.17.11" - } - }, - "xdg-basedir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", - "dev": true - }, - "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "requires": { - "sax": "1.2.1", - "xmlbuilder": "9.0.7" - } - }, - "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "yamljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", - "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", - "requires": { - "argparse": "1.0.10", - "glob": "7.1.4" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } - } - }, - "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", - "dev": true, - "requires": { - "cliui": "4.1.0", - "find-up": "3.0.0", - "get-caller-file": "2.0.5", - "os-locale": "3.1.0", - "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.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "2.2.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "7.0.3", - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "5.2.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", - "dev": true, - "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" - } - }, - "yargs-unparser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", - "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", - "dev": true, - "requires": { - "flat": "4.1.0", - "lodash": "4.17.11", - "yargs": "12.0.5" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "3.0.0" - } - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "2.2.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "3.0.0", - "get-caller-file": "1.0.3", - "os-locale": "3.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "4.0.0", - "yargs-parser": "11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" - } - } - } - } - } -} diff --git a/src/app.js b/src/app.js index 42677871..d5fa7bd7 100644 --- a/src/app.js +++ b/src/app.js @@ -134,7 +134,7 @@ busApi(app, logger); // require('app/permissions')() permissions(); -app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); +app.use(`/${config.apiVersion}/projects/docs`, swaggerUi.serve, swaggerUi.setup(swaggerDocument)); // ======================== // Routes diff --git a/src/events/projectMembers/index.js b/src/events/projectMembers/index.js index 92380e7d..2b6868a6 100644 --- a/src/events/projectMembers/index.js +++ b/src/events/projectMembers/index.js @@ -4,16 +4,12 @@ import _ from 'lodash'; import Promise from 'bluebird'; import config from 'config'; -import { PROJECT_MEMBER_ROLE } from '../../constants'; import util from '../../util'; -import models from '../../models'; -import directProject from '../../services/directProject'; const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); const eClient = util.getElasticSearchClient(); - const updateESPromise = Promise.coroutine(function* a(logger, requestId, projectId, updateDocHandler) { try { const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: projectId }); @@ -43,41 +39,6 @@ const projectMemberAddedHandler = Promise.coroutine(function* a(logger, msg, cha const origRequestId = msg.properties.correlationId; const newMember = JSON.parse(msg.content.toString()); const projectId = newMember.projectId; - const directUpdatePromise = Promise.coroutine(function* () { // eslint-disable-line func-names - if (_.indexOf([PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.MANAGER], newMember.role) > -1) { - // add copilot/update manager permissions operation promise - const directProjectId = yield models.Project.getDirectProjectId(projectId); - if (directProjectId) { - const token = yield util.getM2MToken(); - const req = { - id: origRequestId, - log: logger, - headers: { - authorization: `Bearer ${token}`, - }, - }; - if (newMember.role === PROJECT_MEMBER_ROLE.COPILOT) { - // add copilot to direct project - try { - yield directProject.addCopilot(req, directProjectId, newMember.userId); - logger.info('added copilot to direct'); - } catch (err) { - logger.error('Failed to add copilot, continue..', _.pick(err, ['config', 'data'])); - } - } else { - // update direct project permissions - try { - yield directProject.addManager(req, directProjectId, newMember.userId); - logger.info('added manager to direct'); - } catch (err) { - logger.error('Failed to add manager, continue..', _.pick(err, ['config', 'data'])); - } - } - } else { - logger.debug(`Project# ${projectId} not associated with a Direct project, skipping`); - } - } - }); // handle ES Update // fetch the member information const updateDocPromise = Promise.coroutine(function* (doc) { // eslint-disable-line func-names @@ -92,7 +53,7 @@ const projectMemberAddedHandler = Promise.coroutine(function* a(logger, msg, cha _.remove(invites, invite => invite.email === payload.email || invite.userId === payload.userId); return _.merge(doc._source, { members, invites }); // eslint-disable-line no-underscore-dangle }); - yield Promise.all([directUpdatePromise(), updateESPromise(logger, origRequestId, projectId, updateDocPromise)]); + yield Promise.all([updateESPromise(logger, origRequestId, projectId, updateDocPromise)]); logger.debug('elasticsearch index updated successfully and co-pilot/manager updated in direct project'); channel.ack(msg); } catch (error) { @@ -114,48 +75,11 @@ const projectMemberRemovedHandler = Promise.coroutine(function* (logger, msg, ch const origRequestId = msg.properties.correlationId; const member = JSON.parse(msg.content.toString()); const projectId = member.projectId; - // remove copilot/manager operation promise - const updateDirectProjectPromise = Promise.coroutine(function* () { // eslint-disable-line func-names - if (_.indexOf([PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.MANAGER], member.role) > -1) { - const directProjectId = yield models.Project.getDirectProjectId(projectId); - if (directProjectId) { - const token = yield util.getM2MToken(); - const req = { - id: origRequestId, - log: logger, - headers: { - authorization: `Bearer ${token}`, - }, - }; - if (member.role === PROJECT_MEMBER_ROLE.COPILOT) { - // remove copilot to direct project - try { - yield directProject.deleteCopilot(req, directProjectId, member.userId); - logger.info('removed copilot to direct'); - } catch (err) { - logger.error('Failed to remove copilot, continue..', _.pick(err, ['config', 'data'])); - } - } else { - // remove manager from direct - try { - yield directProject.removeManager(req, directProjectId, member.userId); - logger.info('removed manager to direct'); - } catch (err) { - logger.error('Failed to remove manager, continue..', _.pick(err, ['config', 'data'])); - } - } - } else { - logger.info(`Project# ${projectId} not associated with a Direct project, skipping`); - } - } - }); - const updateDocPromise = (doc) => { const members = _.filter(doc._source.members, single => single.id !== member.id); // eslint-disable-line no-underscore-dangle return Promise.resolve(_.set(doc._source, 'members', members)); // eslint-disable-line no-underscore-dangle }; yield Promise.all([ - updateDirectProjectPromise(), updateESPromise(logger, origRequestId, projectId, updateDocPromise), ]); logger.info('elasticsearch index updated successfully and co-pilot/manager removed in direct project'); diff --git a/src/permissions/index.js b/src/permissions/index.js index 5acab2be..431816ce 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -20,11 +20,13 @@ module.exports = () => { Authorizer.setPolicy('project.edit', projectEdit); Authorizer.setPolicy('project.delete', projectDelete); Authorizer.setPolicy('project.addMember', projectView); + Authorizer.setPolicy('project.viewMember', projectView); Authorizer.setPolicy('project.removeMember', projectMemberDelete); Authorizer.setPolicy('project.addAttachment', projectEdit); Authorizer.setPolicy('project.updateAttachment', projectAttachmentUpdate); Authorizer.setPolicy('project.removeAttachment', projectAttachmentUpdate); Authorizer.setPolicy('project.downloadAttachment', projectAttachmentDownload); + Authorizer.setPolicy('project.listAttachment', projectView); Authorizer.setPolicy('project.updateMember', projectEdit); Authorizer.setPolicy('project.admin', projectAdmin); diff --git a/src/routes/attachments/download.js b/src/routes/attachments/download.js index e88b0b81..b3815e51 100644 --- a/src/routes/attachments/download.js +++ b/src/routes/attachments/download.js @@ -13,7 +13,7 @@ const permissions = tcMiddleware.permissions; const getFileDownloadUrl = (req, filePath) => { if (process.env.NODE_ENV === 'development' || config.get('enableFileUpload') === false) { - return ['dummy://url']; + return ['', 'dummy://url']; } return util.getFileDownloadUrl(req, filePath); }; diff --git a/src/routes/attachments/list.js b/src/routes/attachments/list.js new file mode 100644 index 00000000..d77c7513 --- /dev/null +++ b/src/routes/attachments/list.js @@ -0,0 +1,65 @@ +import _ from 'lodash'; +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; + +/** + * API to get project attachments. + * + */ + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('project.listAttachment'), + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + + util.fetchByIdFromES('attachments', { + query: { + nested: { + path: 'attachments', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'attachments.projectId': projectId } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No attachment found in ES'); + return models.ProjectAttachment.findAll({ + where: { + projectId, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then(attachments => res.json(attachments)) + .catch(next); + } + req.log.debug('attachments found in ES'); + return res.json(data[0].inner_hits.attachments.hits.hits.map(hit => hit._source)); // eslint-disable-line no-underscore-dangle + }) + .catch(next); + }, +]; diff --git a/src/routes/attachments/list.spec.js b/src/routes/attachments/list.spec.js new file mode 100644 index 00000000..07a1f90e --- /dev/null +++ b/src/routes/attachments/list.spec.js @@ -0,0 +1,128 @@ +/* eslint-disable no-unused-expressions */ +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('Project Attachments download', () => { + let project1; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + 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 + return models.ProjectMember.create({ + userId: 40051332, + projectId: project1.id, + role: 'copilot', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }).then(() => models.ProjectAttachment.bulkCreate([{ + projectId: project1.id, + title: 'test.txt', + description: 'blah', + contentType: 'application/unknown', + size: 12312, + category: null, + filePath: 'https://media.topcoder.com/projects/1/test.txt', + createdBy: testUtil.userIds.copilot, + updatedBy: 1, + }, { + projectId: project1.id, + title: 'test2.txt', + description: 'blah 2', + contentType: 'application/unknown', + size: 12312, + category: null, + filePath: 'https://media.topcoder.com/projects/1/test2.txt', + createdBy: testUtil.userIds.copilot, + updatedBy: 1, + }]).then(() => done())); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('List /projects/{id}/attachments', () => { + it('should return 403 for anonymous user', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/attachments`) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/attachments`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send() + .expect(403, done); + }); + + it('should return 200 for manager', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/attachments`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send() + .expect(200, done); + }); + + it('should return 200 for copilot', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/attachments`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send() + .expect(200, done); + }); + + it('should return 200 for admin', (done) => { + request(server) + .get(`/v5/projects/${project1.id}/attachments`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send() + .expect(200) + .end((err, res) => { + 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].projectId.should.be.eql(project1.id); + should.exist(resJson[0].createdAt); + should.exist(resJson[0].updatedAt); + should.not.exist(resJson[0].deletedBy); + should.not.exist(resJson[0].deletedAt); + + done(); + }); + }); + }); +}); diff --git a/src/routes/index.js b/src/routes/index.js index 09dca3cc..adb166b5 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -76,10 +76,6 @@ router.route('/v5/projects') .post(require('./projects/create')) .get(require('./projects/list')); -router.use('/v5/projects/db', compression()); -router.route('/v5/projects/db') - .get(require('./projects/list-db')); - router.route('/v5/projects/admin/es/project/createIndex') .post(require('./admin/project-create-index')); router.route('/v5/projects/admin/es/project/deleteIndex') @@ -95,14 +91,17 @@ router.route('/v5/projects/:projectId(\\d+)') .delete(require('./projects/delete')); router.route('/v5/projects/:projectId(\\d+)/members') + .get(require('./projectMembers/list')) .post(require('./projectMembers/create')); router.route('/v5/projects/:projectId(\\d+)/members/:id(\\d+)') + .get(require('./projectMembers/get')) .delete(require('./projectMembers/delete')) .patch(require('./projectMembers/update')); router.route('/v5/projects/:projectId(\\d+)/attachments') - .post(require('./attachments/create')); + .post(require('./attachments/create')) + .get(require('./attachments/list')); router.route('/v5/projects/:projectId(\\d+)/attachments/:id(\\d+)') .get(require('./attachments/download')) @@ -130,9 +129,6 @@ router.route('/v5/projects/:projectId(\\d+)/phases') .get(require('./phases/list')) .post(require('./phases/create')); -router.route('/v5/projects/:projectId(\\d+)/phases/db') - .get(require('./phases/list-db')); - router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)') .get(require('./phases/get')) .patch(require('./phases/update')) @@ -142,9 +138,6 @@ router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products') .get(require('./phaseProducts/list')) .post(require('./phaseProducts/create')); -router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products/db') - .get(require('./phaseProducts/list-db')); - router.route('/v5/projects/:projectId(\\d+)/phases/:phaseId(\\d+)/products/:productId(\\d+)') .get(require('./phaseProducts/get')) .patch(require('./phaseProducts/update')) diff --git a/src/routes/phaseProducts/list-db.js b/src/routes/phaseProducts/list-db.js deleted file mode 100644 index 64e14db4..00000000 --- a/src/routes/phaseProducts/list-db.js +++ /dev/null @@ -1,44 +0,0 @@ -import _ from 'lodash'; -import { middleware as tcMiddleware } from 'tc-core-library-js'; -import models from '../../models'; - -const permissions = tcMiddleware.permissions; - -module.exports = [ - permissions('project.view'), - async (req, res, next) => { - const projectId = _.parseInt(req.params.projectId); - const phaseId = _.parseInt(req.params.phaseId); - - // check if the project and phase are exist - try { - const countProject = await models.Project.count({ where: { id: projectId } }); - if (countProject === 0) { - const apiErr = new Error(`active project not found for project id ${projectId}`); - apiErr.status = 404; - throw apiErr; - } - - const countPhase = await models.ProjectPhase.count({ where: { id: phaseId } }); - if (countPhase === 0) { - const apiErr = new Error(`active project phase not found for id ${phaseId}`); - apiErr.status = 404; - throw apiErr; - } - } catch (err) { - return next(err); - } - - const parameters = { - projectId, - phaseId, - }; - - try { - const { rows } = await models.PhaseProduct.search(parameters, req.log); - return res.json(rows); - } catch (err) { - return next(err); - } - }, -]; diff --git a/src/routes/phaseProducts/list-db.spec.js b/src/routes/phaseProducts/list-db.spec.js deleted file mode 100644 index 47de2c28..00000000 --- a/src/routes/phaseProducts/list-db.spec.js +++ /dev/null @@ -1,188 +0,0 @@ -/* eslint-disable no-unused-expressions */ -import _ from 'lodash'; -import request from 'supertest'; -import chai from 'chai'; -import server from '../../app'; -import models from '../../models'; -import testUtil from '../../tests/util'; - -const should = chai.should(); - -const body = { - name: 'test phase product', - type: 'product1', - estimatedPrice: 20.0, - actualPrice: 1.23456, - details: { - message: 'This can be any json', - }, - createdBy: 1, - updatedBy: 1, -}; - -describe('Phase Products', () => { - let projectId; - let phaseId; - let project; - const memberUser = { - handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, - userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, - firstName: 'fname', - lastName: 'lName', - email: 'some@abc.com', - }; - const copilotUser = { - handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, - userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, - firstName: 'fname', - lastName: 'lName', - email: 'some@abc.com', - }; - before(function beforeHook(done) { - this.timeout(10000); - // mocks - testUtil.clearDb() - .then(() => { - 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) => { - projectId = p.id; - project = p.toJSON(); - // create members - models.ProjectMember.bulkCreate([{ - id: 1, - userId: copilotUser.userId, - projectId, - role: 'copilot', - isPrimary: false, - createdBy: 1, - updatedBy: 1, - }, { - id: 2, - userId: memberUser.userId, - projectId, - role: 'customer', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }]).then(() => { - models.ProjectPhase.create({ - name: 'test project phase', - status: 'active', - startDate: '2018-05-15T00:00:00Z', - endDate: '2018-05-15T12:00:00Z', - budget: 20.0, - progress: 1.23456, - details: { - message: 'This can be any json', - }, - createdBy: 1, - updatedBy: 1, - projectId, - }).then((phase) => { - phaseId = phase.id; - _.assign(body, { phaseId, projectId }); - project.lastActivityAt = 1; - project.phases = [phase.toJSON()]; - - models.PhaseProduct.create(body).then((product) => { - project.phases[0].products = [product.toJSON()]; - project.lastActivityAt = 1; - done(); - }); - }); - }); - }); - }); - }); - - after((done) => { - testUtil.clearDb(done); - }); - - describe('GET /projects/{id}/phases/{phaseId}/products/db', () => { - it('should return 403 when user have no permission (non team member)', (done) => { - request(server) - .get(`/v5/projects/${projectId}/phases/${phaseId}/products/db`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member2}`, - }) - .send(body) - .expect('Content-Type', /json/) - .expect(403, done); - }); - - it('should return 404 when no project with specific projectId', (done) => { - request(server) - .get(`/v5/projects/999/phases/${phaseId}/products/db`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send(body) - .expect('Content-Type', /json/) - .expect(404, done); - }); - - it('should return 404 when no phase with specific phaseId', (done) => { - request(server) - .get(`/v5/projects/${projectId}/phases/99999/products/db`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send(body) - .expect('Content-Type', /json/) - .expect(404, done); - }); - - it('should return 1 phase when user have project permission (customer)', (done) => { - request(server) - .get(`/v5/projects/${projectId}/phases/${phaseId}/products/db`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(body) - .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 return 1 phase when user have project permission (copilot)', (done) => { - request(server) - .get(`/v5/projects/${projectId}/phases/${phaseId}/products/db`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send(body) - .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/routes/phases/list-db.js b/src/routes/phases/list-db.js deleted file mode 100644 index c4c052c8..00000000 --- a/src/routes/phases/list-db.js +++ /dev/null @@ -1,62 +0,0 @@ -import _ from 'lodash'; -import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; -import models from '../../models'; - -const PHASE_ATTRIBUTES = _.keys(models.ProjectPhase.rawAttributes); -const permissions = tcMiddleware.permissions; - -module.exports = [ - permissions('project.view'), - async (req, res, next) => { - const projectId = _.parseInt(req.params.projectId); - // check if the project is exist - try { - const count = await models.Project.count({ where: { id: projectId } }); - if (count === 0) { - const apiErr = new Error(`active project not found for project id ${projectId}`); - apiErr.status = 404; - throw apiErr; - } - } catch (err) { - return next(err); - } - - // Parse the fields string to determine what fields are to be returned - const rawFields = req.query.fields ? req.query.fields.split(',') : PHASE_ATTRIBUTES; - let sort = req.query.sort ? req.query.sort : 'startDate'; - if (sort && sort.indexOf(' ') === -1) { - sort += ' asc'; - } - const sortableProps = [ - 'startDate asc', 'startDate desc', - 'endDate asc', 'endDate desc', - 'status asc', 'status desc', - 'order asc', 'order desc', - ]; - if (sort && _.indexOf(sortableProps, sort) < 0) { - return util.handleError('Invalid sort criteria', null, req, next); - } - - const sortParameters = sort.split(' '); - - const fields = _.union( - _.intersection(rawFields, [...PHASE_ATTRIBUTES, 'products']), - ['id'], // required fields - ); - - const parameters = { - projectId, - sortField: sortParameters[0], - sortType: sortParameters[1], - fields, - }; - - try { - const { rows } = await models.ProjectPhase.search(parameters, req.log); - return res.json(rows); - } catch (err) { - return next(err); - } - }, -]; diff --git a/src/routes/phases/list-db.spec.js b/src/routes/phases/list-db.spec.js deleted file mode 100644 index aa9876bb..00000000 --- a/src/routes/phases/list-db.spec.js +++ /dev/null @@ -1,159 +0,0 @@ -/* eslint-disable no-unused-expressions */ -import _ from 'lodash'; -import request from 'supertest'; -import chai from 'chai'; -import server from '../../app'; -import models from '../../models'; -import testUtil from '../../tests/util'; - -const should = chai.should(); - -const body = { - name: 'test project phase', - status: 'active', - startDate: '2018-05-15T00:00:00Z', - endDate: '2018-05-15T12:00:00Z', - budget: 20.0, - progress: 1.23456, - details: { - message: 'This can be any json', - }, - createdBy: 1, - updatedBy: 1, -}; - -describe('Project Phases', () => { - let projectId; - let project; - const memberUser = { - handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, - userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, - firstName: 'fname', - lastName: 'lName', - email: 'some@abc.com', - }; - const copilotUser = { - handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, - userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, - firstName: 'fname', - lastName: 'lName', - email: 'some@abc.com', - }; - before(function beforeHook(done) { - this.timeout(10000); - // mocks - testUtil.clearDb() - .then(() => { - 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) => { - projectId = p.id; - project = p.toJSON(); - // create members - models.ProjectMember.bulkCreate([{ - id: 1, - userId: copilotUser.userId, - projectId, - role: 'copilot', - isPrimary: false, - createdBy: 1, - updatedBy: 1, - }, { - id: 2, - userId: memberUser.userId, - projectId, - role: 'customer', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }]).then(() => { - _.assign(body, { projectId }); - return models.ProjectPhase.create(body); - }).then((phase) => { - project.lastActivityAt = 1; - project.phases = [phase]; - done(); - }); - }); - }); - }); - - after((done) => { - testUtil.clearDb(done); - }); - - describe('GET /projects/{id}/phases/db', () => { - it('should return 403 when user have no permission (non team member)', (done) => { - request(server) - .get(`/v5/projects/${projectId}/phases/db`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member2}`, - }) - .send(body) - .expect('Content-Type', /json/) - .expect(403, done); - }); - - it('should return 404 when no project with specific projectId', (done) => { - request(server) - .get('/v5/projects/999/phases/db') - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .send(body) - .expect('Content-Type', /json/) - .expect(404, done); - }); - - it('should return 1 phase when user have project permission (customer)', (done) => { - request(server) - .get(`/v5/projects/${projectId}/phases/db`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send(body) - .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 return 1 phase when user have project permission (copilot)', (done) => { - request(server) - .get(`/v5/projects/${projectId}/phases/db`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send(body) - .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/routes/projectMembers/get.js b/src/routes/projectMembers/get.js new file mode 100644 index 00000000..b64d1574 --- /dev/null +++ b/src/routes/projectMembers/get.js @@ -0,0 +1,78 @@ + + +import _ from 'lodash'; +import Joi from 'joi'; +import validate from 'express-validation'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; + +/** + * API to get project member. + * + */ +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + // handles request validations + validate(schema), + permissions('project.viewMember'), + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const memberRecordId = _.parseInt(req.params.id); + + util.fetchByIdFromES('members', { + query: { + nested: { + path: 'members', + query: + { + filtered: { + filter: { + bool: { + must: [ + { term: { 'members.projectId': projectId } }, + { term: { 'members.id': memberRecordId } }, + ], + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No project member found in ES'); + return models.ProjectMember.findOne({ + where: { + id: memberRecordId, + projectId }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((member) => { + if (!member) { + // check there is an existing member + const err = new Error(`member not found for project id ${projectId}, id ${memberRecordId}`); + err.status = 404; + return next(err); + } + return res.json(member); + }) + .catch(err => next(err)); + } + req.log.debug('project member found in ES'); + return res.json(data[0].inner_hits.members.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + }) + .catch(next); + }, +]; diff --git a/src/routes/projectMembers/get.spec.js b/src/routes/projectMembers/get.spec.js new file mode 100644 index 00000000..fd43ed2f --- /dev/null +++ b/src/routes/projectMembers/get.spec.js @@ -0,0 +1,189 @@ +/** + * Tests for list.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('GET project member', () => { + let projectId; + let memberId; + let memberId2; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + // create members + models.ProjectMember.create({ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }).then((_member) => { + memberId = _member.id; + models.ProjectMember.create({ + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }).then((m) => { + memberId2 = m.id; + done(); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/members/{memberId}', () => { + it('should return 403 for anonymous user', (done) => { + request(server) + .get(`/v5/projects/${projectId}/members/${memberId}`) + .expect(403, done); + }); + + it('should return 404 if requested project doesn\'t exist', (done) => { + request(server) + .get('/v5/projects/9999999/members/1') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 if requested project member doesn\'t exist', (done) => { + request(server) + .get(`/v5/projects/${projectId}/members/9999`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 200 for connect admin', (done) => { + request(server) + .get(`/v5/projects/${projectId}/members/${memberId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(200) + .end(done); + }); + + it('should return 200 for connect manager', (done) => { + request(server) + .get(`/v5/projects/${projectId}/members/${memberId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(200) + .end(done); + }); + + it('should return 200 for member', (done) => { + request(server) + .get(`/v5/projects/${projectId}/members/${memberId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(200, done); + }); + + it('should return 200 for copilot', (done) => { + request(server) + .get(`/v5/projects/${projectId}/members/${memberId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(200, done); + }); + + it('should return 200 for admin when retrieve member with id=1', (done) => { + request(server) + .get(`/v5/projects/${projectId}/members/${memberId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.userId.should.be.eql(_.parseInt(copilotUser.userId)); + resJson.role.should.be.eql('copilot'); + resJson.projectId.should.be.eql(projectId); + should.exist(resJson.createdAt); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + + it('should return 200 for admin when retrieve member with id=2', (done) => { + request(server) + .get(`/v5/projects/${projectId}/members/${memberId2}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.userId.should.be.eql(_.parseInt(memberUser.userId)); + resJson.role.should.be.eql('customer'); + resJson.projectId.should.be.eql(projectId); + should.exist(resJson.createdAt); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + }); +}); diff --git a/src/routes/projectMembers/list.js b/src/routes/projectMembers/list.js new file mode 100644 index 00000000..3c1a76d9 --- /dev/null +++ b/src/routes/projectMembers/list.js @@ -0,0 +1,89 @@ +/** + * API to list all project members + */ +import _ from 'lodash'; +import validate from 'express-validation'; +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 } from '../../constants'; + +const permissions = tcMiddleware.permissions; + +const schema = { + query: { + role: Joi.any() + .valid(PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, + PROJECT_MEMBER_ROLE.COPILOT, + PROJECT_MEMBER_ROLE.CUSTOMER, + PROJECT_MEMBER_ROLE.OBSERVER), + }, + params: { + projectId: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('project.viewMember'), + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const must = [ + { term: { 'members.projectId': projectId } }, + ]; + + if (req.query.role) { + must.push({ term: { 'members.role': req.query.role } }); + } + + util.fetchByIdFromES('members', { + sort: [ + { id: { order: 'asc' } }, + ], + query: { + nested: { + path: 'members', + query: + { + filtered: { + filter: { + bool: { + must, + }, + }, + }, + }, + inner_hits: {}, + }, + }, + }) + .then((data) => { + if (data.length === 0) { + req.log.debug('No project member found in ES'); + // Get all project members + const where = { + projectId, + }; + if (req.query.role) { + where.role = req.query.role; + } + return models.ProjectMember.findAll({ + where, + // Add order + order: [ + ['id', 'ASC'], + ], + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then(members => res.json(members)) + .catch(next); + } + req.log.debug('project member found in ES'); + return res.json(data[0].inner_hits.members.hits.hits.map(hit => hit._source)); // eslint-disable-line no-underscore-dangle + }) + .catch(next); + }, +]; diff --git a/src/routes/projectMembers/list.spec.js b/src/routes/projectMembers/list.spec.js new file mode 100644 index 00000000..19228b50 --- /dev/null +++ b/src/routes/projectMembers/list.spec.js @@ -0,0 +1,176 @@ +/** + * Tests for list.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('LIST project members', () => { + let id; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + id = project.id; + // create members + models.ProjectMember.create({ + id: 1, + userId: copilotUser.userId, + projectId: id, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }).then(() => { + models.ProjectMember.create({ + id: 2, + userId: memberUser.userId, + projectId: id, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }).then(() => done()); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{id}/members', () => { + it('should return 403 for anonymous user', (done) => { + request(server) + .get(`/v5/projects/${id}/members`) + .expect(403, done); + }); + + it('should return 400 for invalid role', (done) => { + request(server) + .get(`/v5/projects/${id}/members?role=invalid`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(400, done); + }); + + it('should return 200 for connect admin', (done) => { + request(server) + .get(`/v5/projects/${id}/members`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(200) + .end(done); + }); + + it('should return 200 for connect manager', (done) => { + request(server) + .get(`/v5/projects/${id}/members`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(200) + .end(done); + }); + + it('should return 200 for member', (done) => { + request(server) + .get(`/v5/projects/${id}/members`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(200, done); + }); + + it('should return 200 for copilot', (done) => { + request(server) + .get(`/v5/projects/${id}/members`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(200, done); + }); + + it('should return 200 for admin', (done) => { + request(server) + .get(`/v5/projects/${id}/members`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.have.length(2); + resJson[0].userId.should.be.eql(copilotUser.userId); + resJson[0].role.should.be.eql('copilot'); + resJson[0].projectId.should.be.eql(id); + should.exist(resJson[0].createdAt); + should.exist(resJson[0].updatedAt); + should.not.exist(resJson[0].deletedBy); + should.not.exist(resJson[0].deletedAt); + + done(); + }); + }); + + it('should return 200 for admin with filter', (done) => { + request(server) + .get(`/v5/projects/${id}/members?role=customer`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.have.length(1); + resJson[0].userId.should.be.eql(_.parseInt(memberUser.userId)); + resJson[0].role.should.be.eql('customer'); + resJson[0].projectId.should.be.eql(id); + should.exist(resJson[0].createdAt); + should.exist(resJson[0].updatedAt); + should.not.exist(resJson[0].deletedBy); + should.not.exist(resJson[0].deletedAt); + + done(); + }); + }); + }); +}); diff --git a/src/routes/projectMembers/update.js b/src/routes/projectMembers/update.js index ae6e5601..68aa0cc8 100644 --- a/src/routes/projectMembers/update.js +++ b/src/routes/projectMembers/update.js @@ -90,34 +90,6 @@ module.exports = [ return Promise.all(operations); }); }) - // .then(() => { - // // TODO move this to an event - // // if copilot role is added or removed should invoke related direct project service - // if(previousValue.role !== newValue.role && (previousValue.role === PROJECT_MEMBER_ROLE.COPILOT - // || newValue.role === PROJECT_MEMBER_ROLE.COPILOT)) { - // return models.Project.getDirectProjectId(projectId) - // .then(directProjectId => { - // if(directProjectId) { - // if(previousValue.role === PROJECT_MEMBER_ROLE.COPILOT) { - // // new role not copilot so remove direct project copilot - // return directProject.deleteCopilot(req, directProjectId, { - // copilotUserId: projectMember.userId - // }) - // } else { - // // new role is copilot so add direct project copilot - // return directProject.addCopilot(req, directProjectId, { - // copilotUserId: projectMember.userId - // }) - // } - // } else { - // return Promise.resolve() - // } - // }) - // - // } else { - // return Promise.resolve() - // } - // }) .then(() => projectMember.reload(projectMember.id)) .then(() => { projectMember = projectMember.get({ plain: true }); diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js index 78ec0787..40278d22 100644 --- a/src/routes/projects/create.js +++ b/src/routes/projects/create.js @@ -11,7 +11,6 @@ import { PROJECT_MEMBER_ROLE, MANAGER_ROLES, PROJECT_STATUS, PROJECT_PHASE_STATU EVENT, RESOURCES, REGEX } from '../../constants'; import fieldLookupValidation from '../../middlewares/fieldLookupValidation'; import util from '../../util'; -import directProject from '../../services/directProject'; const traverse = require('traverse'); @@ -287,27 +286,13 @@ module.exports = [ } req.log.debug('creating project history for project %d', newProject.id); // add to project history asynchronously, don't wait for it to complete - models.ProjectHistory.create({ + return models.ProjectHistory.create({ projectId: newProject.id, status: PROJECT_STATUS.DRAFT, cancelReason: null, updatedBy: req.authUser.userId, }).then(() => req.log.debug('project history created for project %d', newProject.id)) .catch(() => req.log.error('project history failed for project %d', newProject.id)); - req.log.debug('creating direct project for project %d', newProject.id); - return directProject.createDirectProject(req, body) - .then((resp) => { - newProject.directProjectId = resp.data.result.content.projectId; - return newProject.save(); - }) - .then(() => newProject.reload(newProject.id)) - .catch((err) => { - // log the error and continue - req.log.error('Error creating direct project'); - req.log.error(err); - return Promise.resolve(); - }); - // return Promise.resolve(); }); }) .then(() => { diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index 8ee47334..a8e0a0fd 100644 --- a/src/routes/projects/create.spec.js +++ b/src/routes/projects/create.spec.js @@ -338,7 +338,6 @@ describe('Project create', () => { should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); - resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); resJson.type.should.be.eql(body.type); resJson.version.should.be.eql('v3'); @@ -394,7 +393,6 @@ describe('Project create', () => { should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); - resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); resJson.type.should.be.eql(body.type); resJson.version.should.be.eql('v2'); @@ -448,7 +446,6 @@ describe('Project create', () => { should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); - resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); resJson.type.should.be.eql(body.type); resJson.members.should.have.lengthOf(1); @@ -581,7 +578,6 @@ describe('Project create', () => { should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); - resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); resJson.type.should.be.eql(body.type); resJson.version.should.be.eql('v3'); @@ -670,7 +666,6 @@ describe('Project create', () => { should.exist(resJson); should.exist(resJson.billingAccountId); should.exist(resJson.name); - resJson.directProjectId.should.be.eql(128); resJson.status.should.be.eql('draft'); resJson.type.should.be.eql(body.type); resJson.members.should.have.lengthOf(1); diff --git a/src/routes/projects/list-db.js b/src/routes/projects/list-db.js deleted file mode 100644 index 3511a4ff..00000000 --- a/src/routes/projects/list-db.js +++ /dev/null @@ -1,147 +0,0 @@ -import _ from 'lodash'; -import config from 'config'; -import Promise from 'bluebird'; -import models from '../../models'; -import { MANAGER_ROLES } from '../../constants'; -import util from '../../util'; - -/** - * API to handle retrieving projects - * - * Permissions: - * Only users that have access to the project can retrieve it. - * - */ -const PROJECT_ATTRIBUTES = _.without(_.keys(models.Project.rawAttributes), - 'utm', - 'deletedAt', -); -const PROJECT_MEMBER_ATTRIBUTES = _.without( - _.keys(models.ProjectMember.rawAttributes), - 'deletedAt', -); -const PROJECT_ATTACHMENT_ATTRIBUTES = _.without( - _.keys(models.ProjectAttachment.rawAttributes), - 'deletedAt', - -); -const retrieveProjects = (req, criteria, sort, ffields) => { - // order by - const order = sort ? [sort.split(' ')] : [['createdAt', 'asc']]; - let fields = ffields ? ffields.split(',') : []; - // parse the fields string to determine what fields are to be returned - fields = util.parseFields(fields, { - projects: PROJECT_ATTRIBUTES, - project_members: PROJECT_MEMBER_ATTRIBUTES, - }); - // make sure project.id is part of fields - if (_.indexOf(fields.projects, 'id') < 0) fields.projects.push('id'); - const retrieveAttachments = !req.query.fields || req.query.fields.indexOf('attachments') > -1; - const retrieveMembers = !req.query.fields || !!fields.project_members.length; - - return models.Project.searchText({ - filters: criteria.filters, - order, - limit: criteria.limit, - offset: criteria.offset, - attributes: _.get(fields, 'projects', null), - }, req.log) - .then(({ rows, count }) => { - const projectIds = _.map(rows, 'id'); - const promises = []; - // retrieve members - if (projectIds.length && retrieveMembers) { - promises.push( - models.ProjectMember.findAll({ - attributes: _.get(fields, 'ProjectMembers'), - where: { projectId: { $in: projectIds } }, - raw: true, - }), - ); - } - if (projectIds.length && retrieveAttachments) { - promises.push( - models.ProjectAttachment.findAll({ - attributes: PROJECT_ATTACHMENT_ATTRIBUTES, - where: { projectId: { $in: projectIds } }, - raw: true, - }), - ); - } - // return results after promise(s) have resolved - return Promise.all(promises) - .then((values) => { - const allMembers = retrieveMembers ? values.shift() : []; - const allAttachments = retrieveAttachments ? values.shift() : []; - _.forEach(rows, (fp) => { - const p = fp; - // if values length is 1 it could be either attachments or members - if (retrieveMembers) { - p.members = _.filter(allMembers, m => m.projectId === p.id); - } - if (retrieveAttachments) { - p.attachments = _.filter(allAttachments, a => a.projectId === p.id); - } - }); - return { rows, count, pageSize: criteria.limit, page: criteria.page }; - }); - }); -}; - -module.exports = [ - /** - * GET projects/ - * Return a list of projects that match the criteria - */ - (req, res, next) => { - // handle filters - let filters = _.omit(req.query, 'sort', 'perPage', 'page', 'fields'); - - let sort = req.query.sort ? req.query.sort : 'createdAt'; - if (sort && sort.indexOf(' ') === -1) { - sort += ' asc'; - } - const sortableProps = [ - 'createdAt', 'createdAt asc', 'createdAt desc', - 'updatedAt', 'updatedAt asc', 'updatedAt desc', - 'lastActivityAt', 'lastActivityAt asc', 'lastActivityAt desc', - 'id', 'id asc', 'id desc', - 'status', 'status asc', 'status desc', - 'name', 'name asc', 'name desc', - 'type', 'type asc', 'type desc', - ]; - // TODO Add customer and manager filters - if (!util.isValidFilter(filters, ['id', 'status', 'type', 'memberOnly', 'keyword', 'name', 'code']) || - (sort && _.indexOf(sortableProps, sort) < 0)) { - return util.handleError('Invalid filters or sort', null, req, next); - } - // check if user only wants to retrieve projects where he/she is a member - const memberOnly = _.get(filters, 'memberOnly', false); - filters = _.omit(filters, 'memberOnly'); - - const limit = Math.min(req.query.perPage || config.pageSize, config.pageSize); - const criteria = { - filters, - limit, - offset: ((req.query.page - 1) * limit) || 0, - page: req.query.page || 1, - }; - req.log.debug(criteria); - - if (!memberOnly - && (util.hasAdminRole(req) - || util.hasRoles(req, MANAGER_ROLES))) { - // admins & topcoder managers can see all projects - return retrieveProjects(req, criteria, sort, req.query.fields) - .then(result => util.setPaginationHeaders(req, res, result)) - .catch(err => next(err)); - } - - // regular users can only see projects they are members of (or invited, handled bellow) - criteria.filters.userId = req.authUser.userId; - criteria.filters.email = req.authUser.email; - return retrieveProjects(req, criteria, sort, req.query.fields) - .then(result => util.setPaginationHeaders(req, res, result)) - .catch(err => next(err)); - }, -]; diff --git a/src/routes/projects/list-db.spec.js b/src/routes/projects/list-db.spec.js deleted file mode 100644 index 7c66ce00..00000000 --- a/src/routes/projects/list-db.spec.js +++ /dev/null @@ -1,478 +0,0 @@ -/* eslint-disable no-unused-expressions */ -/* eslint-disable max-len */ -import chai from 'chai'; -import request from 'supertest'; - -import models from '../../models'; -import server from '../../app'; -import testUtil from '../../tests/util'; - -const should = chai.should(); - -/** - * Add full text index for projects. - * @return {Promise} returns the promise - */ -function addFullTextIndex() { - if (models.sequelize.options.dialect !== 'postgres') { - return null; - } - - return models.sequelize - .query('ALTER TABLE projects ADD COLUMN "projectFullText" text;') - .then(() => models.sequelize - .query('UPDATE projects SET "projectFullText" = lower(' + - 'name || \' \' || coalesce(description, \'\') || \' \' || coalesce(details#>>\'{utm, code}\', \'\'));')) - .then(() => models.sequelize - .query('CREATE EXTENSION IF NOT EXISTS pg_trgm;')).then(() => models.sequelize - .query('CREATE INDEX project_text_search_idx ON projects USING GIN("projectFullText" gin_trgm_ops);')) - .then(() => models.sequelize - .query('CREATE OR REPLACE FUNCTION project_text_update_trigger() RETURNS trigger AS $$ ' + - 'begin ' + - 'new."projectFullText" := ' + - 'lower(new.name || \' \' || coalesce(new.description, \'\') || \' \' || ' + - ' coalesce(new.details#>>\'{utm, code}\', \'\')); ' + - 'return new; ' + - 'end ' + - '$$ LANGUAGE plpgsql;')) - .then(() => models.sequelize - .query('DROP TRIGGER IF EXISTS project_text_update ON projects;')) - .then(() => models.sequelize - .query('CREATE TRIGGER project_text_update BEFORE INSERT OR UPDATE ON projects' + - ' FOR EACH ROW EXECUTE PROCEDURE project_text_update_trigger();')); -} - -describe('LIST Project db', () => { - let project1; - let project2; - before((done) => { - testUtil.clearDb() - .then(() => addFullTextIndex()) - .then(() => { - const p1 = models.Project.create({ - type: 'generic', - billingAccountId: 1, - name: 'test1', - description: 'test project1', - status: 'active', - details: { - utm: { - code: 'code1', - }, - }, - createdBy: 1, - updatedBy: 1, - lastActivityAt: 1, - lastActivityUserId: '1', - }).then((p) => { - project1 = p; - // create members - const pm1 = models.ProjectMember.create({ - userId: 40051331, - projectId: project1.id, - role: 'customer', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - const pm2 = models.ProjectMember.create({ - userId: 40051332, - projectId: project1.id, - role: 'copilot', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - const pa1 = models.ProjectAttachment.create({ - title: 'Spec', - projectId: project1.id, - description: 'specification', - filePath: 'projects/1/spec.pdf', - contentType: 'application/pdf', - createdBy: 1, - updatedBy: 1, - }); - return Promise.all([pm1, pm2, pa1]); - }); - - const p2 = models.Project.create({ - type: 'visual_design', - billingAccountId: 1, - name: 'test2', - description: 'test project2', - status: 'draft', - details: {}, - createdBy: 1, - updatedBy: 1, - lastActivityAt: 2, - lastActivityUserId: '1', - }).then((p) => { - project2 = p; - return models.ProjectMember.create({ - userId: 40051332, - projectId: project2.id, - role: 'copilot', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }); - }); - const p3 = models.Project.create({ - type: 'visual_design', - billingAccountId: 1, - name: 'test3', - description: 'test project3', - status: 'reviewed', - details: {}, - createdBy: 1, - updatedBy: 1, - lastActivityAt: 3, - lastActivityUserId: '1', - }); - return Promise.all([p1, p2, p3]) - .then(() => done()); - }); - }); - - after((done) => { - testUtil.clearDb(done); - }); - - describe('GET All /projects/', () => { - it('should return 403 if user is not authenticated', (done) => { - request(server) - .get('/v5/projects/db/') - .expect(403, done); - }); - - it('should return 200 and no projects if user does not have access', (done) => { - request(server) - .get(`/v5/projects/db/?id=${project2.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - res.body.should.have.lengthOf(0); - done(); - } - }); - }); - - it('should return the project when registerd member attempts to access the project', (done) => { - request(server) - .get('/v5/projects/db/?status=draft') - .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.have.lengthOf(1); - resJson[0].id.should.equal(project2.id); - done(); - } - }); - }); - - it('should return the project when project that is in reviewed state in which the copilot is its member or has been invited', (done) => { - request(server) - .get('/v5/projects/db/') - .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.have.lengthOf(2); - done(); - } - }); - }); - - it('should return the project for administrator ', (done) => { - request(server) - .get('/v5/projects/db/?fields=id%2Cmembers.id') - .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(3); - done(); - } - }); - }); - - it('should return all projects that match when filtering by name', (done) => { - request(server) - .get('/v5/projects/db/?keyword=test') - .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(3); - done(); - } - }); - }); - - it('should return the project when filtering by keyword, which matches the name', (done) => { - request(server) - .get('/v5/projects/db/?keyword=1') - .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'); - done(); - } - }); - }); - - it('should return the project when filtering by keyword, which matches the description', (done) => { - request(server) - .get('/v5/projects/db/?keyword=project') - .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(3); - done(); - } - }); - }); - - it('should return the project when filtering by keyword, which matches the details', (done) => { - request(server) - .get('/v5/projects/db/?keyword=code') - .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'); - done(); - } - }); - }); - - describe('for connect admin ', () => { - it('should return the project ', (done) => { - request(server) - .get('/v5/projects/db/?fields=id%2Cmembers.id') - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .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(3); - done(); - } - }); - }); - - it('should return all projects that match when filtering by name', (done) => { - request(server) - .get('/v5/projects/db/?keyword=test') - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .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(3); - done(); - } - }); - }); - - it('should return the project when filtering by keyword, which matches the name', (done) => { - request(server) - .get('/v5/projects/db/?keyword=1') - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .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'); - done(); - } - }); - }); - - it('should return the project when filtering by keyword, which matches the description', (done) => { - request(server) - .get('/v5/projects/db/?keyword=project') - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .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(3); - done(); - } - }); - }); - - it('should return the project when filtering by keyword, which matches the details', (done) => { - request(server) - .get('/v5/projects/db/?keyword=code') - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .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'); - done(); - } - }); - }); - - it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt"', (done) => { - request(server) - .get('/v5/projects/db/?sort=lastActivityAt') - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - const resJson = res.body; - should.exist(resJson); - resJson.should.have.lengthOf(3); - resJson[0].name.should.equal('test1'); - resJson[1].name.should.equal('test2'); - resJson[2].name.should.equal('test3'); - done(); - } - }); - }); - - it('should return list of projects ordered descending by lastActivityAt when sort column is "lastActivityAt desc"', (done) => { - request(server) - .get('/v5/projects/db/?sort=lastActivityAt desc') - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - const resJson = res.body; - should.exist(resJson); - resJson.should.have.lengthOf(3); - resJson[0].name.should.equal('test3'); - resJson[1].name.should.equal('test2'); - resJson[2].name.should.equal('test1'); - done(); - } - }); - }); - - it('should return list of projects ordered ascending by lastActivityAt when sort column is "lastActivityAt asc"', (done) => { - request(server) - .get('/v5/projects/db/?sort=lastActivityAt asc') - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .expect(200) - .end((err, res) => { - if (err) { - done(err); - } else { - const resJson = res.body; - should.exist(resJson); - resJson.should.have.lengthOf(3); - resJson[0].name.should.equal('test1'); - resJson[1].name.should.equal('test2'); - resJson[2].name.should.equal('test3'); - done(); - } - }); - }); - }); - }); -}); diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index ebf4bc50..20beea43 100644 --- a/src/routes/projects/update.js +++ b/src/routes/projects/update.js @@ -14,7 +14,6 @@ import { REGEX, } from '../../constants'; import util from '../../util'; -import directProject from '../../services/directProject'; const traverse = require('traverse'); @@ -198,20 +197,6 @@ module.exports = [ project.set(newValues); return project.save(); }) - .then(() => { - if (updatedProps.billingAccountId && - (previousValue.billingAccountId !== updatedProps.billingAccountId)) { - if (!previousValue.directProjectId) { - return Promise.resolve(); - } - // if billing account is updated and exist direct projectId we - // should invoke direct project service - return directProject.addBillingAccount(req, previousValue.directProjectId, { - billingAccountId: updatedProps.billingAccountId, - }); - } - return Promise.resolve(); - }) .then(() => project.reload(project.id)) // update project history .then(() => new Promise((accept, reject) => { diff --git a/src/routes/projects/update.spec.js b/src/routes/projects/update.spec.js index b79da2d1..78ebe966 100644 --- a/src/routes/projects/update.spec.js +++ b/src/routes/projects/update.spec.js @@ -1,5 +1,4 @@ /* eslint-disable no-unused-expressions */ -import _ from 'lodash'; import chai from 'chai'; import sinon from 'sinon'; import request from 'supertest'; @@ -7,7 +6,6 @@ import request from 'supertest'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; -import util from '../../util'; import busApi from '../../services/busApi'; @@ -552,51 +550,7 @@ describe('Project', () => { }); }); - it('should return 500 if error to sync billing account id', (done) => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - post: () => Promise.reject(new Error('error message')), - }); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); - request(server) - .patch(`/v5/projects/${project1.id}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - billingAccountId: 123, - - }) - .expect('Content-Type', /json/) - .expect(500) - .end((err, res) => { - if (err) { - done(err); - } else { - res.body.message.should.equal('error message'); - done(); - } - }); - }); - it('should return 200 and sync new billing account id', (done) => { - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - post: () => Promise.resolve({ - status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: { - billingAccountName: '2', - }, - }, - }, - }), - }); - const postSpy = sinon.spy(mockHttpClient, 'post'); - sandbox.stub(util, 'getHttpClient', () => mockHttpClient); request(server) .patch(`/v5/projects/${project1.id}`) .set({ @@ -617,7 +571,6 @@ describe('Project', () => { resJson.billingAccountId.should.equal(123); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); resJson.updatedBy.should.equal(40051332); - postSpy.should.have.been.calledOnce; server.services.pubsub.publish.calledWith('project.updated').should.be.true; done(); } diff --git a/src/services/directProject.js b/src/services/directProject.js deleted file mode 100644 index 6d4c0e42..00000000 --- a/src/services/directProject.js +++ /dev/null @@ -1,124 +0,0 @@ - -import _ from 'lodash'; -import config from 'config'; -import util from '../util'; - -/** - * Service methods to handle direct project. - */ - -/** - * Build custom http client for request - * @param {Object} req request - * @returns {Object} custom http client - * @private - */ -function getHttpClient(req) { - const httpClient = util.getHttpClient(req); - httpClient.defaults.headers.common.Authorization = req.headers.authorization; - httpClient.defaults.headers.common['Content-Type'] = 'application/json'; - httpClient.defaults.baseURL = config.get('directProjectServiceEndpoint'); - httpClient.defaults.timeout = _.get(config, 'directProjectServiceTimeout', 5000); - httpClient.interceptors.response.use((resp) => { - // req.log.debug('resp: ', JSON.stringify(resp.data, null, 2)) - if (resp.status !== 200 || resp.data.result.status !== 200) { - req.log.error('error resp: ', JSON.stringify(resp.data, null, 2)); - return Promise.reject(new Error(resp.data.result.content.message)); - } - return Promise.resolve(resp); - }); - return httpClient; -} - -export default { - /** - * Create direct project - * @param {Object} req request - * @param {Object} body the body contains project information - * @returns {Promise} create direct project promise - */ - createDirectProject: (req, body) => getHttpClient(req) - .post('/projects/', body), - - /** - * Add direct project copilot - * @param {Object} req request - * @param {Object} directProjectId the id of direct project - * @param {Integer} copilotUserId copilot user idenitifier - * @returns {Promise} add pilot promise - */ - addCopilot: (req, directProjectId, copilotUserId) => getHttpClient(req) - .post(`/projects/${directProjectId}/copilot`, { copilotUserId }), - - /** - * Remove direct project copilot - * @param {Object} req the request - * @param {Integer} directProjectId the id of direct project - * @param {Integer} copilotUserId copilot user idenitifier - * @returns {Promise} response promise - */ - deleteCopilot: (req, directProjectId, copilotUserId) => getHttpClient(req).request({ - method: 'delete', - url: `/projects/${directProjectId}/copilot`, - data: { copilotUserId }, - }), - - /** - * Add billing account for direct project - * @param {Object} req the request - * @param {String} directProjectId the id of direct project - * @param {Object} body the body contains billing account information - * @returns {Promise} add billing account promise - */ - addBillingAccount: (req, directProjectId, body) => getHttpClient(req) - .post(`/projects/${directProjectId}/billingaccount`, body), - - /** - * Add/remove direct project permissions - * This can be used to add/remove direct project manager - * @param {Object} req the request - * @param {Integer} directProjectId the id of direct project - * @param {Object} body the body contains permissions information - * @returns {Promise} promise - */ - editProjectPermissions: (req, directProjectId, body) => getHttpClient(req) - .post(`/projects/${directProjectId}/permissions`, body), - - /** - * Add direct project manager - * @param {Object} req request - * @param {Object} directProjectId the id of direct project - * @param {Object} userId user idenitifier - * @returns {Promise} add pilot promise - */ - addManager: (req, directProjectId, userId) => getHttpClient(req) - .post(`/projects/${directProjectId}/permissions`, { - permissions: [{ - userId, - permissionType: { - permissionTypeId: 3, - name: 'project_full', - }, - studio: false, - }], - }), - - /** - * Remove direct project manager - * @param {Object} req the request - * @param {Integer} directProjectId the id of direct project - * @param {Object} userId user idenitifier - * @returns {Promise} response promise - */ - deleteManager: (req, directProjectId, userId) => getHttpClient(req) - .post(`/projects/${directProjectId}/permissions`, { - permissions: [{ - userId, - permissionType: { - permissionTypeId: '', - name: 'project_full', - }, - studio: false, - }], - }), -}; From 058992663ae8ea213b29cbc432f00ed0963d0ecd Mon Sep 17 00:00:00 2001 From: gets0ul Date: Wed, 16 Oct 2019 19:10:31 +0700 Subject: [PATCH 06/88] Merge changes from 'dev' branch --- .eslintrc | 2 +- config/custom-environment-variables.json | 3 +- config/default.json | 4 +- migrations/20190502_status_history_create.sql | 29 ++ ...extract_scope_from_project_templates_2.sql | 12 + .../20190620_migrate_product_templates.sql | 11 + migrations/20190624_workStream.sql | 82 ++++ ..._settings_and_project_estimation_items.sql | 74 +++ .../20190720_project_building_block.sql | 35 ++ ...ect_phase_description_and_requirements.sql | 8 + migrations/20190729_scope_change_requests.sql | 42 ++ .../20190927_milestone_texts_not_required.sql | 8 + src/constants.js | 71 +++ src/events/milestones/index.js | 35 +- src/events/projectPhases/index.js | 174 +++++-- src/models/buildingBlock.js | 57 +++ src/models/milestone.js | 104 ++++- src/models/phaseWorkStream.js | 14 + src/models/planConfig.js | 1 + src/models/priceConfig.js | 1 + src/models/project.js | 2 + src/models/projectEstimation.js | 54 +-- src/models/projectEstimationItem.js | 164 +++++++ src/models/projectPhase.js | 35 +- src/models/projectSetting.js | 115 +++++ src/models/scopeChangeRequest.js | 68 +++ src/models/statusHistory.js | 35 ++ src/models/versionModelClassMethods.js | 11 + src/models/workManagementPermission.js | 35 ++ src/models/workStream.js | 42 ++ src/permissions/constants.js | 39 ++ src/permissions/copilotAndAbove.js | 29 +- src/permissions/index.js | 40 +- src/permissions/project.delete.js | 8 +- src/permissions/projectMember.delete.js | 7 +- src/permissions/projectSetting.edit.js | 40 ++ src/permissions/workManagementForTemplate.js | 56 +++ src/routes/attachments/create.js | 4 +- src/routes/attachments/delete.js | 3 +- src/routes/attachments/download.js | 4 + src/routes/form/version/getVersion.js | 4 +- src/routes/index.js | 70 +++ src/routes/metadata/list.js | 22 +- src/routes/metadata/list.spec.js | 53 ++- src/routes/milestoneTemplates/clone.js | 2 +- src/routes/milestoneTemplates/create.js | 6 +- src/routes/milestones/create.js | 16 +- src/routes/milestones/create.spec.js | 83 +--- src/routes/milestones/delete.js | 7 +- src/routes/milestones/list.spec.js | 48 +- src/routes/milestones/update.js | 45 +- src/routes/milestones/update.spec.js | 222 ++++++++- src/routes/permissions/get.js | 65 +++ src/routes/permissions/get.spec.js | 233 ++++++++++ src/routes/phaseProducts/create.spec.js | 4 +- src/routes/phaseProducts/delete.spec.js | 6 +- src/routes/phaseProducts/list.spec.js | 4 +- src/routes/phaseProducts/update.spec.js | 8 +- src/routes/phases/create.js | 6 +- src/routes/phases/create.spec.js | 2 +- src/routes/phases/delete.js | 4 +- src/routes/phases/delete.spec.js | 4 +- src/routes/phases/list.js | 2 +- src/routes/phases/list.spec.js | 4 +- src/routes/phases/update.js | 6 +- src/routes/phases/update.spec.js | 6 +- src/routes/planConfig/version/create.spec.js | 9 +- src/routes/planConfig/version/delete.spec.js | 18 +- src/routes/planConfig/version/get.spec.js | 9 +- src/routes/planConfig/version/getVersion.js | 10 +- src/routes/planConfig/version/update.spec.js | 9 +- src/routes/priceConfig/version/getVersion.js | 10 +- src/routes/projectEstimationItems/list.js | 49 ++ .../projectEstimationItems/list.spec.js | 233 ++++++++++ src/routes/projectMemberInvites/create.js | 129 +++++- .../projectMemberInvites/create.spec.js | 170 +++++-- src/routes/projectMembers/create.js | 68 ++- src/routes/projectMembers/update.js | 13 +- src/routes/projectReports/LookAuth.js | 73 +++ src/routes/projectReports/LookRun.js | 106 +++++ src/routes/projectReports/getReport.js | 56 +++ src/routes/projectReports/mock.js | 25 + .../mockFiles/projectBudget.json | 57 +++ .../projectReports/mockFiles/summary.json | 17 + src/routes/projectSettings/create.js | 94 ++++ src/routes/projectSettings/create.spec.js | 382 ++++++++++++++++ src/routes/projectSettings/delete.js | 63 +++ src/routes/projectSettings/delete.spec.js | 281 ++++++++++++ src/routes/projectSettings/list.js | 51 +++ src/routes/projectSettings/list.spec.js | 243 ++++++++++ src/routes/projectSettings/update.js | 76 ++++ src/routes/projectSettings/update.spec.js | 418 +++++++++++++++++ src/routes/projects/create.js | 314 ++++++++++--- src/routes/projects/create.spec.js | 25 - src/routes/projects/delete.spec.js | 8 +- src/routes/projects/get.js | 4 + src/routes/projects/get.spec.js | 11 +- src/routes/projects/list.spec.js | 4 +- src/routes/projects/update.js | 61 ++- src/routes/scopeChangeRequests/create.js | 85 ++++ src/routes/scopeChangeRequests/create.spec.js | 280 ++++++++++++ src/routes/scopeChangeRequests/update.js | 127 ++++++ src/routes/scopeChangeRequests/update.spec.js | 213 +++++++++ src/routes/timelines/list.spec.js | 4 +- src/routes/workItems/create.js | 133 ++++++ src/routes/workItems/create.spec.js | 340 ++++++++++++++ src/routes/workItems/delete.js | 92 ++++ src/routes/workItems/delete.spec.js | 287 ++++++++++++ src/routes/workItems/get.js | 74 +++ src/routes/workItems/get.spec.js | 234 ++++++++++ src/routes/workItems/list.js | 63 +++ src/routes/workItems/list.spec.js | 225 +++++++++ src/routes/workItems/update.js | 117 +++++ src/routes/workItems/update.spec.js | 408 +++++++++++++++++ .../workManagementPermissions/create.js | 59 +++ .../workManagementPermissions/create.spec.js | 203 +++++++++ .../workManagementPermissions/delete.js | 37 ++ .../workManagementPermissions/delete.spec.js | 211 +++++++++ src/routes/workManagementPermissions/get.js | 38 ++ .../workManagementPermissions/get.spec.js | 149 ++++++ src/routes/workManagementPermissions/list.js | 43 ++ .../workManagementPermissions/list.spec.js | 241 ++++++++++ .../workManagementPermissions/update.js | 81 ++++ .../workManagementPermissions/update.spec.js | 248 ++++++++++ src/routes/workStreams/create.js | 70 +++ src/routes/workStreams/create.spec.js | 255 +++++++++++ src/routes/workStreams/delete.js | 44 ++ src/routes/workStreams/delete.spec.js | 168 +++++++ src/routes/workStreams/get.js | 41 ++ src/routes/workStreams/get.spec.js | 162 +++++++ src/routes/workStreams/list.js | 45 ++ src/routes/workStreams/list.spec.js | 157 +++++++ src/routes/workStreams/update.js | 65 +++ src/routes/workStreams/update.spec.js | 230 ++++++++++ src/routes/works/create.js | 159 +++++++ src/routes/works/create.spec.js | 355 +++++++++++++++ src/routes/works/delete.js | 73 +++ src/routes/works/delete.spec.js | 288 ++++++++++++ src/routes/works/get.js | 58 +++ src/routes/works/get.spec.js | 216 +++++++++ src/routes/works/list.js | 96 ++++ src/routes/works/list.spec.js | 224 +++++++++ src/routes/works/update.js | 189 ++++++++ src/routes/works/update.spec.js | 426 ++++++++++++++++++ src/services/messageService.js | 16 +- src/tests/mockRabbitMQ.js | 18 + src/util.js | 338 +++++++++++++- 147 files changed, 12761 insertions(+), 510 deletions(-) create mode 100644 migrations/20190502_status_history_create.sql create mode 100644 migrations/20190611_extract_scope_from_project_templates_2.sql create mode 100644 migrations/20190620_migrate_product_templates.sql create mode 100644 migrations/20190624_workStream.sql create mode 100644 migrations/20190719_project_settings_and_project_estimation_items.sql create mode 100644 migrations/20190720_project_building_block.sql create mode 100644 migrations/20190729_project_phase_description_and_requirements.sql create mode 100644 migrations/20190729_scope_change_requests.sql create mode 100644 migrations/20190927_milestone_texts_not_required.sql create mode 100644 src/models/buildingBlock.js create mode 100644 src/models/phaseWorkStream.js create mode 100644 src/models/projectEstimationItem.js create mode 100644 src/models/projectSetting.js create mode 100644 src/models/scopeChangeRequest.js create mode 100644 src/models/statusHistory.js create mode 100644 src/models/workManagementPermission.js create mode 100644 src/models/workStream.js create mode 100644 src/permissions/constants.js create mode 100644 src/permissions/projectSetting.edit.js create mode 100644 src/permissions/workManagementForTemplate.js create mode 100644 src/routes/permissions/get.js create mode 100644 src/routes/permissions/get.spec.js create mode 100644 src/routes/projectEstimationItems/list.js create mode 100644 src/routes/projectEstimationItems/list.spec.js create mode 100644 src/routes/projectReports/LookAuth.js create mode 100644 src/routes/projectReports/LookRun.js create mode 100644 src/routes/projectReports/getReport.js create mode 100644 src/routes/projectReports/mock.js create mode 100644 src/routes/projectReports/mockFiles/projectBudget.json create mode 100644 src/routes/projectReports/mockFiles/summary.json create mode 100644 src/routes/projectSettings/create.js create mode 100644 src/routes/projectSettings/create.spec.js create mode 100644 src/routes/projectSettings/delete.js create mode 100644 src/routes/projectSettings/delete.spec.js create mode 100644 src/routes/projectSettings/list.js create mode 100644 src/routes/projectSettings/list.spec.js create mode 100644 src/routes/projectSettings/update.js create mode 100644 src/routes/projectSettings/update.spec.js create mode 100644 src/routes/scopeChangeRequests/create.js create mode 100644 src/routes/scopeChangeRequests/create.spec.js create mode 100644 src/routes/scopeChangeRequests/update.js create mode 100644 src/routes/scopeChangeRequests/update.spec.js create mode 100644 src/routes/workItems/create.js create mode 100644 src/routes/workItems/create.spec.js create mode 100644 src/routes/workItems/delete.js create mode 100644 src/routes/workItems/delete.spec.js create mode 100644 src/routes/workItems/get.js create mode 100644 src/routes/workItems/get.spec.js create mode 100644 src/routes/workItems/list.js create mode 100644 src/routes/workItems/list.spec.js create mode 100644 src/routes/workItems/update.js create mode 100644 src/routes/workItems/update.spec.js create mode 100644 src/routes/workManagementPermissions/create.js create mode 100644 src/routes/workManagementPermissions/create.spec.js create mode 100644 src/routes/workManagementPermissions/delete.js create mode 100644 src/routes/workManagementPermissions/delete.spec.js create mode 100644 src/routes/workManagementPermissions/get.js create mode 100644 src/routes/workManagementPermissions/get.spec.js create mode 100644 src/routes/workManagementPermissions/list.js create mode 100644 src/routes/workManagementPermissions/list.spec.js create mode 100644 src/routes/workManagementPermissions/update.js create mode 100644 src/routes/workManagementPermissions/update.spec.js create mode 100644 src/routes/workStreams/create.js create mode 100644 src/routes/workStreams/create.spec.js create mode 100644 src/routes/workStreams/delete.js create mode 100644 src/routes/workStreams/delete.spec.js create mode 100644 src/routes/workStreams/get.js create mode 100644 src/routes/workStreams/get.spec.js create mode 100644 src/routes/workStreams/list.js create mode 100644 src/routes/workStreams/list.spec.js create mode 100644 src/routes/workStreams/update.js create mode 100644 src/routes/workStreams/update.spec.js create mode 100644 src/routes/works/create.js create mode 100644 src/routes/works/create.spec.js create mode 100644 src/routes/works/delete.js create mode 100644 src/routes/works/delete.spec.js create mode 100644 src/routes/works/get.js create mode 100644 src/routes/works/get.spec.js create mode 100644 src/routes/works/list.js create mode 100644 src/routes/works/list.spec.js create mode 100644 src/routes/works/update.js create mode 100644 src/routes/works/update.spec.js create mode 100644 src/tests/mockRabbitMQ.js diff --git a/.eslintrc b/.eslintrc index b0115e3f..00dbe70b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,7 @@ "mocha": true }, "rules": { - "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/*.spec.js", "**/serviceMocks.js"]}], + "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/*.spec.js", "src/tests/*.js"]}], "max-len": ["error", { "ignoreComments": true, "code": 120 }], "valid-jsdoc": ["error", { "requireReturn": true, diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index 48896676..a44ecae3 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -52,5 +52,6 @@ "accountsAppUrl": "ACCOUNTS_APP_URL", "inviteEmailSubject": "INVITE_EMAIL_SUBJECT", "inviteEmailSectionTitle": "INVITE_EMAIL_SECTION_TITLE", - "pageSize": "PAGE_SIZE" + "pageSize": "PAGE_SIZE", + "SSO_REFCODES": "SSO_REFCODES" } diff --git a/config/default.json b/config/default.json index 4b62d372..8ea02f6a 100644 --- a/config/default.json +++ b/config/default.json @@ -56,5 +56,7 @@ "accountsAppUrl": "https://accounts.topcoder-dev.com", "MAX_REVISION_NUMBER": 100, "UNIQUE_GMAIL_VALIDATION": false, - "pageSize": 20 + "pageSize": 20, + "VALID_STATUSES_BEFORE_PAUSED": "[\"active\"]", + "SSO_REFCODES": "[]" } diff --git a/migrations/20190502_status_history_create.sql b/migrations/20190502_status_history_create.sql new file mode 100644 index 00000000..cac38750 --- /dev/null +++ b/migrations/20190502_status_history_create.sql @@ -0,0 +1,29 @@ +-- +-- Create table status history +-- + +CREATE TABLE status_history ( + id bigint, + "reference" character varying(45) NOT NULL, + "referenceId" bigint NOT NULL, + "status" character varying(45) NOT NULL, + "comment" text, + "createdAt" timestamp with time zone, + "updatedAt" timestamp with time zone, + "createdBy" integer NOT NULL, + "updatedBy" integer NOT NULL +); + +CREATE SEQUENCE status_history_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE status_history_id_seq OWNED BY status_history.id; + +ALTER TABLE ONLY status_history ALTER COLUMN id SET DEFAULT nextval('status_history_id_seq'::regclass); + +ALTER TABLE ONLY status_history + ADD CONSTRAINT status_history_pkey PRIMARY KEY (id); \ No newline at end of file diff --git a/migrations/20190611_extract_scope_from_project_templates_2.sql b/migrations/20190611_extract_scope_from_project_templates_2.sql new file mode 100644 index 00000000..0a0cbaa3 --- /dev/null +++ b/migrations/20190611_extract_scope_from_project_templates_2.sql @@ -0,0 +1,12 @@ +-- +-- FIX for 20190316_extract_scope_from_project_templates.sql +-- apply created auto-increments sequences to `id` columns + +ALTER TABLE form + ALTER COLUMN id SET DEFAULT nextval('form_id_seq'); + +ALTER TABLE price_config + ALTER COLUMN id SET DEFAULT nextval('price_config_id_seq'); + +ALTER TABLE plan_config + ALTER COLUMN id SET DEFAULT nextval('plan_config_id_seq'); diff --git a/migrations/20190620_migrate_product_templates.sql b/migrations/20190620_migrate_product_templates.sql new file mode 100644 index 00000000..fdd6631a --- /dev/null +++ b/migrations/20190620_migrate_product_templates.sql @@ -0,0 +1,11 @@ +-- +-- UPDATE EXISTING TABLES: +-- template: +-- remove `sections` if exists and change `questions` to `sections` + +-- +-- product_templates + +UPDATE product_templates +SET template = (template::jsonb #- '{questions}' #- '{sections}') || jsonb_build_object('sections', template::jsonb ->'questions') +WHERE template::jsonb ? 'questions'; diff --git a/migrations/20190624_workStream.sql b/migrations/20190624_workStream.sql new file mode 100644 index 00000000..fa975def --- /dev/null +++ b/migrations/20190624_workStream.sql @@ -0,0 +1,82 @@ +-- +-- CREATE NEW TABLE: +-- work_streams +-- +CREATE TABLE work_streams ( + id bigint NOT NULL, + "name" character varying(255) NOT NULL, + "type" character varying(45) NOT NULL, + "status" character varying(255) NOT NULL, + "projectId" bigint NOT NULL, + "deletedAt" timestamp with time zone, + "createdAt" timestamp with time zone, + "updatedAt" timestamp with time zone, + "deletedBy" bigint, + "createdBy" bigint NOT NULL, + "updatedBy" bigint NOT NULL +); + +CREATE SEQUENCE work_streams_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE work_streams_id_seq OWNED BY work_streams.id; + +ALTER TABLE work_streams + ALTER COLUMN id SET DEFAULT nextval('work_streams_id_seq'); + +ALTER TABLE ONLY work_streams + ADD CONSTRAINT "work_streams_pkey" PRIMARY KEY (id); + +ALTER TABLE ONLY work_streams + ADD CONSTRAINT "work_streams_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES projects(id) ON UPDATE CASCADE ON DELETE SET NULL; + +-- +-- CREATE NEW TABLE: +-- work_management_permissions +-- +CREATE TABLE work_management_permissions ( + id bigint NOT NULL, + "policy" character varying(255) NOT NULL, + "permission" json NOT NULL, + "projectTemplateId" bigint NOT NULL, + "deletedAt" timestamp with time zone, + "createdAt" timestamp with time zone, + "updatedAt" timestamp with time zone, + "deletedBy" bigint, + "createdBy" bigint NOT NULL, + "updatedBy" bigint NOT NULL +); + +CREATE SEQUENCE work_management_permissions_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE work_management_permissions_id_seq OWNED BY work_management_permissions.id; + +ALTER TABLE work_management_permissions + ALTER COLUMN id SET DEFAULT nextval('work_management_permissions_id_seq'); + +-- +-- CREATE NEW TABLE: +-- phase_work_streams +-- +CREATE TABLE phase_work_streams ( + "workStreamId" bigint NOT NULL, + "phaseId" bigint NOT NULL +); + +ALTER TABLE ONLY phase_work_streams + ADD CONSTRAINT "phase_work_streams_pkey" PRIMARY KEY ("workStreamId", "phaseId"); + +ALTER TABLE ONLY phase_work_streams + ADD CONSTRAINT "phase_work_streams_phaseId_fkey" FOREIGN KEY ("phaseId") REFERENCES project_phases(id) ON UPDATE CASCADE ON DELETE CASCADE; + +ALTER TABLE ONLY phase_work_streams + ADD CONSTRAINT "phase_work_streams_workStreamId_fkey" FOREIGN KEY ("workStreamId") REFERENCES work_streams(id) ON UPDATE CASCADE ON DELETE CASCADE; diff --git a/migrations/20190719_project_settings_and_project_estimation_items.sql b/migrations/20190719_project_settings_and_project_estimation_items.sql new file mode 100644 index 00000000..cf6086b0 --- /dev/null +++ b/migrations/20190719_project_settings_and_project_estimation_items.sql @@ -0,0 +1,74 @@ +-- CREATE NEW TABLES: +-- project_settings +-- project_estimation_items +-- + +-- +-- project_settings +-- + +CREATE TABLE project_settings ( + id bigint NOT NULL, + key character varying(255), + value character varying(255), + "valueType" character varying(255), + "projectId" bigint NOT NULL, + metadata json NOT NULL DEFAULT '{}'::json, + "readPermission" json NOT NULL DEFAULT '{}'::json, + "writePermission" json NOT NULL DEFAULT '{}'::json, + "deletedAt" timestamp with time zone, + "createdAt" timestamp with time zone, + "updatedAt" timestamp with time zone, + "deletedBy" bigint, + "createdBy" bigint NOT NULL, + "updatedBy" bigint NOT NULL, + CONSTRAINT project_settings_pkey PRIMARY KEY (id) +); + +CREATE SEQUENCE project_settings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE project_settings_id_seq OWNED BY project_settings.id; + +ALTER TABLE project_settings + ALTER COLUMN id SET DEFAULT nextval('project_settings_id_seq'); + +ALTER TABLE project_settings + ADD CONSTRAINT project_settings_key_project_id UNIQUE (key, "projectId"); + +-- +-- project_estimation_items +-- + +CREATE TABLE project_estimation_items ( + id bigint NOT NULL, + "projectEstimationId" bigint NOT NULL, + price double precision NOT NULL, + type character varying(255) NOT NULL, + "markupUsedReference" character varying(255) NOT NULL, + "markupUsedReferenceId" bigint NOT NULL, + metadata json NOT NULL DEFAULT '{}'::json, + "deletedAt" timestamp with time zone, + "createdAt" timestamp with time zone, + "updatedAt" timestamp with time zone, + "deletedBy" bigint, + "createdBy" bigint NOT NULL, + "updatedBy" bigint NOT NULL, + CONSTRAINT project_estimation_items_pkey PRIMARY KEY (id) +); + +CREATE SEQUENCE project_estimation_items_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE project_estimation_items_id_seq OWNED BY form.id; + +ALTER TABLE project_estimation_items + ALTER COLUMN id SET DEFAULT nextval('project_estimation_items_id_seq'); diff --git a/migrations/20190720_project_building_block.sql b/migrations/20190720_project_building_block.sql new file mode 100644 index 00000000..2342ea0a --- /dev/null +++ b/migrations/20190720_project_building_block.sql @@ -0,0 +1,35 @@ +-- +-- CREATE NEW TABLE: +-- building_blocks +-- +CREATE TABLE building_blocks ( + id bigint NOT NULL, + "key" character varying(255) NOT NULL, + "config" json NOT NULL DEFAULT '{}'::json, + "privateConfig" json NOT NULL DEFAULT '{}'::json, + "deletedAt" timestamp with time zone, + "createdAt" timestamp with time zone, + "updatedAt" timestamp with time zone, + "deletedBy" bigint, + "createdBy" bigint NOT NULL, + "updatedBy" bigint NOT NULL +); + +ALTER TABLE building_blocks + ADD CONSTRAINT building_blocks_key_uniq UNIQUE (key); + +CREATE SEQUENCE building_blocks_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE building_blocks_id_seq OWNED BY building_blocks.id; + +ALTER TABLE building_blocks + ALTER COLUMN id SET DEFAULT nextval('building_blocks_id_seq'); + +ALTER TABLE ONLY building_blocks + ADD CONSTRAINT building_blocks_pkey PRIMARY KEY (id); + diff --git a/migrations/20190729_project_phase_description_and_requirements.sql b/migrations/20190729_project_phase_description_and_requirements.sql new file mode 100644 index 00000000..9ad9e35e --- /dev/null +++ b/migrations/20190729_project_phase_description_and_requirements.sql @@ -0,0 +1,8 @@ +-- +-- UPDATE EXISTING TABLES: +-- project_phases +-- description column: added +-- requirements column: added + +ALTER TABLE project_phases ADD COLUMN "description" character varying(255); +ALTER TABLE project_phases ADD COLUMN "requirements" text; \ No newline at end of file diff --git a/migrations/20190729_scope_change_requests.sql b/migrations/20190729_scope_change_requests.sql new file mode 100644 index 00000000..fe382c33 --- /dev/null +++ b/migrations/20190729_scope_change_requests.sql @@ -0,0 +1,42 @@ +-- +-- CREATE NEW TABLE: +-- scope_change_requests +-- + +CREATE TABLE scope_change_requests +( + id bigint NOT NULL, + "projectId" bigint NOT NULL, + "oldScope" json NOT NULL, + "newScope" json NOT NULL, + status character varying(45) NOT NULL, + "deletedAt" timestamp with time zone, + "createdAt" timestamp with time zone, + "updatedAt" timestamp with time zone, + "approvedAt" timestamp with time zone, + "deletedBy" integer, + "createdBy" integer NOT NULL, + "updatedBy" integer NOT NULL, + "approvedBy" integer +); + + +CREATE SEQUENCE scope_change_requests_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE scope_change_requests_id_seq OWNED BY scope_change_requests.id; + +ALTER TABLE scope_change_requests + ALTER COLUMN id SET DEFAULT nextval('scope_change_requests_id_seq'); + +ALTER TABLE ONLY scope_change_requests + ADD CONSTRAINT scope_change_requests_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY scope_change_requests + ADD CONSTRAINT "scope_change_requests_projectId_fkey" FOREIGN KEY ("projectId") + REFERENCES projects(id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE; diff --git a/migrations/20190927_milestone_texts_not_required.sql b/migrations/20190927_milestone_texts_not_required.sql new file mode 100644 index 00000000..bda1c110 --- /dev/null +++ b/migrations/20190927_milestone_texts_not_required.sql @@ -0,0 +1,8 @@ +-- +-- UPDATE EXISTING TABLES: +-- milestones + +ALTER TABLE milestones ALTER COLUMN "plannedText" DROP NOT NULL; +ALTER TABLE milestones ALTER COLUMN "activeText" DROP NOT NULL; +ALTER TABLE milestones ALTER COLUMN "completedText" DROP NOT NULL; +ALTER TABLE milestones ALTER COLUMN "blockedText" DROP NOT NULL; \ No newline at end of file diff --git a/src/constants.js b/src/constants.js index dd4d25f3..7cf68b0d 100644 --- a/src/constants.js +++ b/src/constants.js @@ -9,6 +9,13 @@ export const PROJECT_STATUS = { CANCELLED: 'cancelled', }; +export const WORKSTREAM_STATUS = { + DRAFT: 'draft', + REVIEWED: 'reviewed', + ACTIVE: 'active', + COMPLETED: 'completed', + PAUSED: 'paused', +}; export const PROJECT_PHASE_STATUS = PROJECT_STATUS; export const MILESTONE_STATUS = PROJECT_STATUS; @@ -19,12 +26,20 @@ export const PROJECT_MEMBER_ROLE = { CUSTOMER: 'customer', COPILOT: 'copilot', ACCOUNT_MANAGER: 'account_manager', + PROGRAM_MANAGER: 'program_manager', + ACCOUNT_EXECUTIVE: 'account_executive', + SOLUTION_ARCHITECT: 'solution_architect', + PROJECT_MANAGER: 'project_manager', }; export const PROJECT_MEMBER_MANAGER_ROLES = [ PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.OBSERVER, PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, + PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, ]; export const USER_ROLE = { @@ -34,6 +49,12 @@ export const USER_ROLE = { COPILOT: 'Connect Copilot', CONNECT_ADMIN: 'Connect Admin', COPILOT_MANAGER: 'Connect Copilot Manager', + BUSINESS_DEVELOPMENT_REPRESENTATIVE: 'Business Development Representative', + PRESALES: 'Presales', + ACCOUNT_EXECUTIVE: 'Account Executive', + PROGRAM_MANAGER: 'Program Manager', + SOLUTION_ARCHITECT: 'Solution Architect', + PROJECT_MANAGER: 'Project Manager', }; export const ADMIN_ROLES = [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN]; @@ -43,6 +64,13 @@ export const MANAGER_ROLES = [ USER_ROLE.MANAGER, USER_ROLE.TOPCODER_ACCOUNT_MANAGER, USER_ROLE.COPILOT_MANAGER, + USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE, + USER_ROLE.PRESALES, + USER_ROLE.ACCOUNT_EXECUTIVE, + + USER_ROLE.PROGRAM_MANAGER, + USER_ROLE.SOLUTION_ARCHITECT, + USER_ROLE.PROJECT_MANAGER, ]; export const EVENT = { @@ -162,6 +190,11 @@ export const TIMELINE_REFERENCES = { PROJECT: 'project', PHASE: 'phase', PRODUCT: 'product', + WORK: 'work', +}; + +export const STATUS_HISTORY_REFERENCES = { + MILESTONE: 'milestone', }; export const MILESTONE_TEMPLATE_REFERENCES = { @@ -178,6 +211,44 @@ export const INVITE_STATUS = { CANCELED: 'canceled', }; +export const SCOPE_CHANGE_REQ_STATUS = { + PENDING: 'pending', + APPROVED: 'approved', + REJECTED: 'rejected', + ACTIVATED: 'activated', + CANCELED: 'canceled', +}; +export const MAX_PARALLEL_REQUEST_QTY = 10; + +export const ROUTES = { + PHASE_PRODUCTS: { + UPDATE: 'phase_products.update', + }, + PHASES: { + UPDATE: 'phases.update', + }, + WORKS: { + UPDATE: 'works.update', + }, + WORK_ITEMS: { + UPDATE: 'work_items.update', + }, +}; + +export const ESTIMATION_TYPE = { + FEE: 'fee', + COMMUNITY: 'community', + TOPCODER_SERVICE: 'topcoder_service', +}; + +export const VALUE_TYPE = { + INT: 'int', + DOUBLE: 'double', + STRING: 'string', + PERCENTAGE: 'percentage', +}; + + export const RESOURCES = { PROJECT: 'project', PROJECT_TEMPLATE: 'project.template', diff --git a/src/events/milestones/index.js b/src/events/milestones/index.js index d8f884a9..83698255 100644 --- a/src/events/milestones/index.js +++ b/src/events/milestones/index.js @@ -82,35 +82,14 @@ const milestoneUpdatedHandler = Promise.coroutine(function* (logger, msg, channe }); } - // if (data.original.order !== data.updated.order) { - // const milestoneWithSameOrder = - // _.find(milestones, milestone => milestone.id !== data.updated.id && milestone.order === data.updated.order); - // if (milestoneWithSameOrder) { - // // Increase the order from M to K: if there is an item with order K, - // // orders from M+1 to K should be made M to K-1 - // if (data.original.order < data.updated.order) { - // _.each(milestones, (single) => { - // if (single.id !== data.updated.id - // && (data.original.order + 1) <= single.order - // && single.order <= data.updated.order) { - // single.order -= 1; // eslint-disable-line no-param-reassign - // } - // }); - // } else { - // // Decrease the order from M to K: if there is an item with order K, - // // orders from K to M-1 should be made K+1 to M - // _.each(milestones, (single) => { - // if (single.id !== data.updated.id - // && data.updated.order <= single.order - // && single.order <= (data.original.order - 1)) { - // single.order += 1; // eslint-disable-line no-param-reassign - // } - // }); - // } - // } - // } + let updatedTimeline = doc._source; // eslint-disable-line no-underscore-dangle + // if timeline has been modified during milestones updates + if (data.cascadedUpdates && data.cascadedUpdates.timeline && data.cascadedUpdates.timeline.updated) { + // merge updated timeline with the object in ES index, the same way as we do when updating timeline in ES using timeline endpoints + updatedTimeline = _.merge(doc._source, data.cascadedUpdates.timeline.updated); // eslint-disable-line no-underscore-dangle + } - const merged = _.assign(doc._source, { milestones }); // eslint-disable-line no-underscore-dangle + const merged = _.assign(updatedTimeline, { milestones }); yield eClient.update({ index: ES_TIMELINE_INDEX, type: ES_TIMELINE_TYPE, diff --git a/src/events/projectPhases/index.js b/src/events/projectPhases/index.js index d9e8c218..e8e33379 100644 --- a/src/events/projectPhases/index.js +++ b/src/events/projectPhases/index.js @@ -7,6 +7,8 @@ import config from 'config'; import _ from 'lodash'; import Promise from 'bluebird'; import util from '../../util'; +import { TIMELINE_REFERENCES } from '../../constants'; + import messageService from '../../services/messageService'; const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); @@ -14,6 +16,39 @@ const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); const eClient = util.getElasticSearchClient(); +/** + * Build topics data based on route parameter. + * + * @param {Object} logger logger to log along with trace id + * @param {Object} phase phase object + * @param {String} route route value can be PHASE/WORK + * @returns {undefined} + */ +const buildTopicsData = (logger, phase, route) => { + if (route === TIMELINE_REFERENCES.WORK) { + return [{ + tag: `work#${phase.id}-details`, + title: `${phase.name} - Details`, + reference: 'project', + referenceId: `${phase.projectId}`, + body: 'This is the beginning of your phase discussion. During execution of this phase, all related communication will be conducted here - phase updates, questions and answers, suggestions, etc. If you haven\'t already, do please take a moment to review the form in the Specification tab above and fill in as much detail as possible. This will help get started faster. Thanks!', // eslint-disable-line + }, { + tag: `work#${phase.id}-requirements`, + title: `${phase.name} - Requirements`, + reference: 'project', + referenceId: `${phase.projectId}`, + body: 'This is the beginning of your phase discussion. During execution of this phase, all related communication will be conducted here - phase updates, questions and answers, suggestions, etc. If you haven\'t already, do please take a moment to review the form in the Specification tab above and fill in as much detail as possible. This will help get started faster. Thanks!', // eslint-disable-line + }]; + } + return [{ + tag: `phase#${phase.id}`, + title: phase.name, + reference: 'project', + referenceId: `${phase.projectId}`, + body: 'This is the beginning of your phase discussion. During execution of this phase, all related communication will be conducted here - phase updates, questions and answers, suggestions, etc. If you haven\'t already, do please take a moment to review the form in the Specification tab above and fill in as much detail as possible. This will help get started faster. Thanks!', // eslint-disable-line + }]; +}; + /** * Indexes the project phase in the elastic search. * @@ -59,26 +94,22 @@ const indexProjectPhase = Promise.coroutine(function* (logger, phase) { // eslin }); /** - * Creates a new phase topic in message api. + * Creates topics in message api * * @param {Object} logger logger to log along with trace id - * @param {Object} msg event payload + * @param {Object} phase phase object + * @param {String} route route value can be `phase`/`work` * @returns {undefined} */ -const createPhaseTopic = Promise.coroutine(function* (logger, phase) { // eslint-disable-line func-names +const createTopics = Promise.coroutine(function* (logger, phase, route) { // eslint-disable-line func-names try { - logger.debug('Creating topic for phase with phase', phase); - const topic = yield messageService.createTopic({ - reference: 'project', - referenceId: `${phase.projectId}`, - tag: `phase#${phase.id}`, - title: phase.name, - body: 'This is the beginning of your phase discussion. During execution of this phase, all related communication will be conducted here - phase updates, questions and answers, suggestions, etc. If you haven\'t already, do please take a moment to review the form in the Specification tab above and fill in as much detail as possible. This will help get started faster. Thanks!', // eslint-disable-line - }, logger); - logger.debug('topic for the phase created successfully'); - logger.debug('created topic', topic); + logger.debug(`Creating topics for ${route} with phase`, phase); + const topicsData = buildTopicsData(logger, phase, route); + const topics = yield Promise.all(_.map(topicsData, topicData => messageService.createTopic(topicData, logger))); + logger.debug(`topics for the ${route} created successfully`); + logger.debug('created topics', topics); } catch (error) { - logger.error('Error in creating topic for the project phase', error); + logger.error(`Error in creating topic for ${route}`, error); // don't throw the error back to nack the bus, because we don't want to get multiple topics per phase // we can create topic for a phase manually, if somehow it fails } @@ -92,12 +123,14 @@ const createPhaseTopic = Promise.coroutine(function* (logger, phase) { // eslint * @returns {undefined} */ const projectPhaseAddedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names - const phase = JSON.parse(msg.content.toString()); + const data = JSON.parse(msg.content.toString()); + const phase = _.get(data, 'added', {}); + const route = _.get(data, 'route', 'PHASE'); try { logger.debug('calling indexProjectPhase', phase); yield indexProjectPhase(logger, phase, channel); logger.debug('calling createPhaseTopic', phase); - yield createPhaseTopic(logger, phase); + yield createTopics(logger, phase, route); channel.ack(msg); } catch (error) { logger.error('Error handling project.phase.added event', error); @@ -135,31 +168,46 @@ const updateIndexProjectPhase = Promise.coroutine(function* (logger, data) { // }); /** - * Creates a new phase topic in message api. + * Update one topic + * + * @param {Object} logger logger to log along with trace id + * @param {Object} phase phase object + * @param {Object} topicUpdate updated topic data + * @returns {undefined} + */ +const updateOneTopic = Promise.coroutine(function* (logger, phase, topicUpdate) { // eslint-disable-line func-names + const topic = yield messageService.getTopicByTag(phase.projectId, topicUpdate.tag, logger); + logger.trace('Topic', topic); + const title = topicUpdate.title; + const titleChanged = topic && topic.title !== title; + logger.trace('titleChanged', titleChanged); + const contentPost = topic && topic.posts && topic.posts.length > 0 ? topic.posts[0] : null; + logger.trace('contentPost', contentPost); + const postId = _.get(contentPost, 'id'); + const content = _.get(contentPost, 'body'); + if (postId && content && titleChanged) { + const updatedTopic = yield messageService.updateTopic(topic.id, { title, postId, content }, logger); + logger.debug('topic updated successfully'); + logger.trace('updated topic', updatedTopic); + } +}); + +/** + * Update topics in message api. * * @param {Object} logger logger to log along with trace id - * @param {Object} msg event payload + * @param {Object} phase phase object + * @param {String} route route value can be `phase`/`work` * @returns {undefined} */ -const updatePhaseTopic = Promise.coroutine(function* (logger, phase) { // eslint-disable-line func-names +const updateTopics = Promise.coroutine(function* (logger, phase, route) { // eslint-disable-line func-names try { - logger.debug('Updating topic for phase with phase', phase); - const topic = yield messageService.getPhaseTopic(phase.projectId, phase.id, logger); - logger.trace('Topic', topic); - const title = phase.name; - const titleChanged = topic && topic.title !== title; - logger.trace('titleChanged', titleChanged); - const contentPost = topic && topic.posts && topic.posts.length > 0 ? topic.posts[0] : null; - logger.trace('contentPost', contentPost); - const postId = _.get(contentPost, 'id'); - const content = _.get(contentPost, 'body'); - if (postId && content && titleChanged) { - const updatedTopic = yield messageService.updateTopic(topic.id, { title, postId, content }, logger); - logger.debug('topic for the phase updated successfully'); - logger.trace('updated topic', updatedTopic); - } + logger.debug(`Updating topic for ${route} with phase`, phase); + const topicsData = buildTopicsData(logger, phase, route); + yield Promise.all(_.map(topicsData, topicData => updateOneTopic(logger, phase, topicData))); + logger.debug(`topics for the ${route} updated successfully`); } catch (error) { - logger.error('Error in updating topic for the project phase', error); + logger.error(`Error in updating topic for ${route}`, error); // don't throw the error back to nack the bus, because we don't want to get multiple topics per phase // we can create topic for a phase manually, if somehow it fails } @@ -175,10 +223,11 @@ const updatePhaseTopic = Promise.coroutine(function* (logger, phase) { // eslint const projectPhaseUpdatedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names try { const data = JSON.parse(msg.content.toString()); + const route = _.get(data, 'route', 'PHASE'); logger.debug('calling updateIndexProjectPhase', data); yield updateIndexProjectPhase(logger, data, channel); - logger.debug('calling updatePhaseTopic', data.updated); - yield updatePhaseTopic(logger, data.updated); + logger.debug('calling updateTopics', data.updated); + yield updateTopics(logger, data.updated, route); channel.ack(msg); } catch (error) { logger.error('Error handling project.phase.updated event', error); @@ -197,13 +246,14 @@ const projectPhaseUpdatedHandler = Promise.coroutine(function* (logger, msg, cha const removePhaseFromIndex = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names try { const data = JSON.parse(msg.content.toString()); - const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: data.projectId }); - const phases = _.filter(doc._source.phases, single => single.id !== data.id); // eslint-disable-line no-underscore-dangle + const phase = _.get(data, 'deleted', {}); + const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: phase.projectId }); + const phases = _.filter(doc._source.phases, single => single.id !== phase.id); // eslint-disable-line no-underscore-dangle const merged = _.assign(doc._source, { phases }); // eslint-disable-line no-underscore-dangle yield eClient.update({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, - id: data.projectId, + id: phase.projectId, body: { doc: merged, }, @@ -217,26 +267,46 @@ const removePhaseFromIndex = Promise.coroutine(function* (logger, msg) { // esli }); /** - * Removes the phase topic from the message api. + * Removes one topic from the message api. * - * @param {Object} logger logger to log along with trace id - * @param {Object} msg event payload + * @param {Object} logger logger to log along with trace id + * @param {Object} phase phase object + * @param {Object} tag topic tag * @returns {undefined} */ -const removePhaseTopic = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names +const removeOneTopic = Promise.coroutine(function* (logger, phase, tag) { // eslint-disable-line func-names try { - const phase = JSON.parse(msg.content.toString()); - const phaseTopic = yield messageService.getPhaseTopic(phase.projectId, phase.id, logger); + const phaseTopic = yield messageService.getTopicByTag(phase.projectId, tag, logger); yield messageService.deletePosts(phaseTopic.id, phaseTopic.postIds, logger); yield messageService.deleteTopic(phaseTopic.id, logger); - logger.debug('topic for the phase removed successfully'); } catch (error) { - logger.error('Error in removing topic for the project phase', error); + logger.error(`Error removing topic by tab ${tag}`, error); // don't throw the error back to nack the bus // we can delete topic for a phase manually, if somehow it fails } }); +/** + * Remove topics in message api. + * + * @param {Object} logger logger to log along with trace id + * @param {Object} phase phase object + * @param {String} route route value can be `phase`/`work` + * @returns {undefined} + */ +const removeTopics = Promise.coroutine(function* (logger, phase, route) { // eslint-disable-line func-names + try { + logger.debug(`Removing topic for ${route} with phase`, phase); + const topicsData = buildTopicsData(logger, phase, route); + yield Promise.all(_.map(topicsData, topicData => removeOneTopic(logger, phase, topicData.tag))); + logger.debug(`topics for the ${route} removed successfully`); + } catch (error) { + logger.error(`Error in removing topic for ${route}`, error); + // don't throw the error back to nack the bus, because we don't want to get multiple topics per phase + // we can create topic for a phase manually, if somehow it fails + } +}); + /** * Handler for project phase deleted event * @param {Object} logger logger to log along with trace id @@ -247,7 +317,11 @@ const removePhaseTopic = Promise.coroutine(function* (logger, msg) { // eslint-d const projectPhaseRemovedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names try { yield removePhaseFromIndex(logger, msg, channel); - yield removePhaseTopic(logger, msg); + const data = JSON.parse(msg.content.toString()); + const phase = _.get(data, 'deleted', {}); + const route = _.get(data, 'route'); + logger.debug('calling removeTopics'); + yield removeTopics(logger, phase, route); channel.ack(msg); } catch (error) { logger.error('Error fetching project document from elasticsearch', error); @@ -261,5 +335,5 @@ module.exports = { projectPhaseAddedHandler, projectPhaseRemovedHandler, projectPhaseUpdatedHandler, - createPhaseTopic, + createPhaseTopic: createTopics, }; diff --git a/src/models/buildingBlock.js b/src/models/buildingBlock.js new file mode 100644 index 00000000..a28762a4 --- /dev/null +++ b/src/models/buildingBlock.js @@ -0,0 +1,57 @@ +/* eslint-disable valid-jsdoc */ +/** + * BuildingBlock model + * + * WARNING: This model contains sensitive data! + * + * - To return data from this model to the user always use methods `find`/`findAll` which would + * filter out the sensitive data which should be never returned to the user. + * - For internal usage you can use `options.includePrivateConfigForInternalUsage` + * which would force `find`/`findAll` to return fields which contain sensitive data. + * Use the data returned in such way ONLY FOR INTERNAL usage. It means such data can be used + * to make some calculations inside Project Service but it should be never returned to the user as it is. + */ +module.exports = (sequelize, DataTypes) => { + const BuildingBlock = sequelize.define('BuildingBlock', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + key: { type: DataTypes.STRING(255), allowNull: false, unique: true }, + config: { type: DataTypes.JSON, allowNull: false, defaultValue: {} }, + privateConfig: { type: DataTypes.JSON, allowNull: false, defaultValue: {} }, + deletedAt: DataTypes.DATE, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + deletedBy: DataTypes.BIGINT, + createdBy: { type: DataTypes.BIGINT, allowNull: false }, + updatedBy: { type: DataTypes.BIGINT, allowNull: false }, + }, { + tableName: 'building_blocks', + paranoid: true, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + deletedAt: 'deletedAt', + hooks: { + /** + * Inside before hook we are evaluating if user has permission to retrieve `privateConfig` field. + * If no, we remove this field from the attributes list, so this field is not requested and thus + * not returned. + * + * @param {Object} options find/findAll options + */ + afterFind: function removePrivateConfig(buildingBlocks, options) { + // ONLY FOR INTERNAL USAGE: don't use this option to return the data by API + if (!options.includePrivateConfigForInternalUsage) { + // try to remove privateConfig from result + buildingBlocks.map((block) => { + const b = block; + delete b.privateConfig; + return b; + }); + } + return buildingBlocks; + }, + }, + }); + + return BuildingBlock; +}; diff --git a/src/models/milestone.js b/src/models/milestone.js index 1f01ae70..644f7b0c 100644 --- a/src/models/milestone.js +++ b/src/models/milestone.js @@ -1,6 +1,54 @@ +import _ from 'lodash'; import moment from 'moment'; +import models from '../models'; +import { STATUS_HISTORY_REFERENCES } from '../constants'; /* eslint-disable valid-jsdoc */ +/** + * Populate and map milestone model with statusHistory + * NOTE that this function mutates milestone + * + * @param {Array|Object} milestone one milestone or list of milestones + * @param {Object} options options which has been used to call main method + * + * @returns {Promise} promise + */ +const populateWithStatusHistory = async (milestone, options) => { + // depend on this option `milestone` is a sequlize ORM object or plain JS object + const isRaw = !!_.get(options, 'raw'); + const getMilestoneId = m => ( + isRaw ? m.id : m.dataValues.id + ); + const formatMilestone = statusHistory => ( + isRaw ? { statusHistory } : { dataValues: { statusHistory } } + ); + if (Array.isArray(milestone)) { + const allStatusHistory = await models.StatusHistory.findAll({ + where: { + referenceId: { $in: milestone.map(getMilestoneId) }, + reference: 'milestone', + }, + order: [['createdAt', 'desc']], + raw: true, + }); + + return milestone.map((m, index) => { + const statusHistory = _.filter(allStatusHistory, { referenceId: getMilestoneId(m) }); + return _.merge(milestone[index], formatMilestone(statusHistory)); + }); + } + + const statusHistory = await models.StatusHistory.findAll({ + where: { + referenceId: getMilestoneId(milestone), + reference: 'milestone', + }, + order: [['createdAt', 'desc']], + raw: true, + }); + return _.merge(milestone, formatMilestone(statusHistory)); +}; + /** * The Milestone model */ @@ -18,10 +66,10 @@ module.exports = (sequelize, DataTypes) => { type: { type: DataTypes.STRING(45), allowNull: false }, details: DataTypes.JSON, order: { type: DataTypes.INTEGER, allowNull: false }, - plannedText: { type: DataTypes.STRING(512), allowNull: false }, - activeText: { type: DataTypes.STRING(512), allowNull: false }, - completedText: { type: DataTypes.STRING(512), allowNull: false }, - blockedText: { type: DataTypes.STRING(512), allowNull: false }, + plannedText: { type: DataTypes.STRING(512) }, + activeText: { type: DataTypes.STRING(512) }, + completedText: { type: DataTypes.STRING(512) }, + blockedText: { type: DataTypes.STRING(512) }, hidden: { type: DataTypes.BOOLEAN, defaultValue: false }, deletedAt: DataTypes.DATE, createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, @@ -36,6 +84,54 @@ module.exports = (sequelize, DataTypes) => { updatedAt: 'updatedAt', createdAt: 'createdAt', deletedAt: 'deletedAt', + hooks: { + afterCreate: (milestone, options) => models.StatusHistory.create({ + reference: STATUS_HISTORY_REFERENCES.MILESTONE, + referenceId: milestone.id, + status: milestone.status, + comment: null, + createdBy: milestone.createdBy, + updatedBy: milestone.updatedBy, + }, { + transaction: options.transaction, + }).then(() => populateWithStatusHistory(milestone, options)), + + afterBulkCreate: (milestones, options) => { + const listStatusHistory = milestones.map(({ dataValues }) => ({ + reference: STATUS_HISTORY_REFERENCES.MILESTONE, + referenceId: dataValues.id, + status: dataValues.status, + comment: null, + createdBy: dataValues.createdBy, + updatedBy: dataValues.updatedBy, + })); + + return models.StatusHistory.bulkCreate(listStatusHistory, { + transaction: options.transaction, + }).then(() => populateWithStatusHistory(milestones, options)); + }, + + afterUpdate: (milestone, options) => { + if (milestone.changed().includes('status')) { + return models.StatusHistory.create({ + reference: STATUS_HISTORY_REFERENCES.MILESTONE, + referenceId: milestone.id, + status: milestone.status, + comment: options.comment || null, + createdBy: milestone.createdBy, + updatedBy: milestone.updatedBy, + }, { + transaction: options.transaction, + }).then(() => populateWithStatusHistory(milestone)); + } + return populateWithStatusHistory(milestone, options); + }, + + afterFind: (milestone, options) => { + if (!milestone) return Promise.resolve(); + return populateWithStatusHistory(milestone, options); + }, + }, }); /** diff --git a/src/models/phaseWorkStream.js b/src/models/phaseWorkStream.js new file mode 100644 index 00000000..d420f21b --- /dev/null +++ b/src/models/phaseWorkStream.js @@ -0,0 +1,14 @@ +/* eslint-disable valid-jsdoc */ + +/** + * The PhaseWorkStream model + */ + +module.exports = (sequelize) => { + const PhaseWorkStream = sequelize.define('PhaseWorkStream', {}, + { + tableName: 'phase_work_streams', + }); + + return PhaseWorkStream; +}; diff --git a/src/models/planConfig.js b/src/models/planConfig.js index 8231a686..4db619c9 100644 --- a/src/models/planConfig.js +++ b/src/models/planConfig.js @@ -42,6 +42,7 @@ module.exports = (sequelize, DataTypes) => { PlanConfig.latestVersion = classMethods.latestVersion; PlanConfig.latestRevisionOfLatestVersion = classMethods.latestRevisionOfLatestVersion; PlanConfig.latestVersionIncludeUsed = classMethods.latestVersionIncludeUsed; + PlanConfig.findOneWithLatestRevision = classMethods.findOneWithLatestRevision; return PlanConfig; }; diff --git a/src/models/priceConfig.js b/src/models/priceConfig.js index a954344d..31f4a92a 100644 --- a/src/models/priceConfig.js +++ b/src/models/priceConfig.js @@ -41,6 +41,7 @@ module.exports = (sequelize, DataTypes) => { PriceConfig.latestVersion = classMethods.latestVersion; PriceConfig.latestRevisionOfLatestVersion = classMethods.latestRevisionOfLatestVersion; PriceConfig.latestVersionIncludeUsed = classMethods.latestVersionIncludeUsed; + PriceConfig.findOneWithLatestRevision = classMethods.findOneWithLatestRevision; return PriceConfig; }; diff --git a/src/models/project.js b/src/models/project.js index b7bd9647..6cfb9d03 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -67,6 +67,8 @@ module.exports = function defineProject(sequelize, DataTypes) { Project.hasMany(models.ProjectAttachment, { as: 'attachments', foreignKey: 'projectId' }); Project.hasMany(models.ProjectPhase, { as: 'phases', foreignKey: 'projectId' }); Project.hasMany(models.ProjectMemberInvite, { as: 'memberInvites', foreignKey: 'projectId' }); + Project.hasMany(models.ScopeChangeRequest, { as: 'scopeChangeRequests', foreignKey: 'projectId' }); + Project.hasMany(models.WorkStream, { as: 'workStreams', foreignKey: 'projectId' }); }; /** diff --git a/src/models/projectEstimation.js b/src/models/projectEstimation.js index 5bc2a7ec..9ab0d8b9 100644 --- a/src/models/projectEstimation.js +++ b/src/models/projectEstimation.js @@ -1,33 +1,29 @@ -module.exports = function defineProjectHistory(sequelize, DataTypes) { - const ProjectEstimation = sequelize.define( - 'ProjectEstimation', - { - id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, - buildingBlockKey: { type: DataTypes.STRING, allowNull: false }, - conditions: { type: DataTypes.STRING, allowNull: false }, - price: { type: DataTypes.DOUBLE, allowNull: false }, - quantity: { type: DataTypes.INTEGER, allowNull: true }, - minTime: { type: DataTypes.INTEGER, allowNull: false }, - maxTime: { type: DataTypes.INTEGER, allowNull: false }, - metadata: { type: DataTypes.JSON, allowNull: false, defaultValue: {} }, - projectId: { type: DataTypes.BIGINT, allowNull: false }, +module.exports = function defineProjectEstimation(sequelize, DataTypes) { + const ProjectEstimation = sequelize.define('ProjectEstimation', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + buildingBlockKey: { type: DataTypes.STRING, allowNull: false }, + conditions: { type: DataTypes.STRING, allowNull: false }, + price: { type: DataTypes.DOUBLE, allowNull: false }, + quantity: { type: DataTypes.INTEGER, allowNull: true }, + minTime: { type: DataTypes.INTEGER, allowNull: false }, + maxTime: { type: DataTypes.INTEGER, allowNull: false }, + metadata: { type: DataTypes.JSON, allowNull: false, defaultValue: {} }, + projectId: { type: DataTypes.BIGINT, allowNull: false }, - deletedAt: { type: DataTypes.DATE, allowNull: true }, - createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - deletedBy: DataTypes.BIGINT, - createdBy: { type: DataTypes.INTEGER, allowNull: false }, - updatedBy: { type: DataTypes.INTEGER, allowNull: false }, - }, - { - tableName: 'project_estimations', - paranoid: true, - timestamps: true, - updatedAt: 'updatedAt', - createdAt: 'createdAt', - indexes: [], - }, - ); + deletedAt: { type: DataTypes.DATE, allowNull: true }, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + deletedBy: DataTypes.BIGINT, + createdBy: { type: DataTypes.INTEGER, allowNull: false }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false }, + }, { + tableName: 'project_estimations', + paranoid: true, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + indexes: [], + }); return ProjectEstimation; }; diff --git a/src/models/projectEstimationItem.js b/src/models/projectEstimationItem.js new file mode 100644 index 00000000..0e0aeb87 --- /dev/null +++ b/src/models/projectEstimationItem.js @@ -0,0 +1,164 @@ +/* eslint-disable valid-jsdoc */ +/** + * ProjectEstimationItem model + * + * WARNING: This model contains sensitive data! + * + * - To return data from this model to the user always use methods `find`/`findAll` + * and provide to them `options.reqUser` and `options.members` to check what + * types of Project Estimation Items user which makes the request can get. + * - For internal usage you can use `options.includeAllProjectEstimatinoItemsForInternalUsage` + * which would force `find`/`findAll` to return all the records without checking permissions. + * Use the data returned in such way ONLY FOR INTERNAL usage. It means such data can be used + * to make some calculations inside Project Service but it should be never returned to the user as it is. + */ +import _ from 'lodash'; +import util from '../util'; +import { + ESTIMATION_TYPE, + MANAGER_ROLES, + PROJECT_MEMBER_ROLE, +} from '../constants'; + +/* + This config defines which Project Estimation Item `types` users can get + based on their permissions + */ +const permissionsConfigs = [ + // Topcoder managers can get all types of Project Estimation Items + { + permission: { topcoderRoles: MANAGER_ROLES }, + types: _.values(ESTIMATION_TYPE), + }, + + // Project Copilots can get only 'community' type of Project Estimation Items + { + permission: { projectRoles: PROJECT_MEMBER_ROLE.COPILOT }, + types: [ESTIMATION_TYPE.COMMUNITY], + }, +]; + +module.exports = function defineProjectEstimationItem(sequelize, DataTypes) { + const ProjectEstimationItem = sequelize.define( + 'ProjectEstimationItem', + { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + projectEstimationId: { type: DataTypes.BIGINT, allowNull: false }, // ProjectEstimation id + price: { type: DataTypes.DOUBLE, allowNull: false }, + type: { + type: DataTypes.STRING, + allowNull: false, + validate: { + isIn: [_.values(ESTIMATION_TYPE)], + }, + }, + markupUsedReference: { type: DataTypes.STRING, allowNull: false }, + markupUsedReferenceId: { type: DataTypes.BIGINT, allowNull: false }, // ProjectSetting id + metadata: { type: DataTypes.JSON, allowNull: false, defaultValue: {} }, + deletedAt: { type: DataTypes.DATE, allowNull: true }, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + deletedBy: DataTypes.INTEGER, + createdBy: { type: DataTypes.INTEGER, allowNull: false }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false }, + }, + { + tableName: 'project_estimation_items', + paranoid: true, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + indexes: [], + hooks: { + /** + * Inside before hook we are evaluating what Project Estimation Item types current user may retrieve. + * We update `where` query so only allowed types may be retrieved. + * + * @param {Object} options find/findAll options + * @param {Function} callback callback after hook + */ + beforeFind: (options, callback) => { + // ONLY FOR INTERNAL USAGE: don't use this option to return the data by API + if (options.includeAllProjectEstimatinoItemsForInternalUsage) { + return callback ? callback(null) : null; + } + + if (!options.reqUser || !options.members) { + const err = new Error('You must provide auth user and project members to get project estimation items'); + if (!callback) throw err; + return callback(err); + } + + // find all project estimation item types which are allowed to be returned to the user + let allowedTypes = []; + permissionsConfigs.forEach((permissionsConfig) => { + if (util.hasPermission(permissionsConfig.permission, options.reqUser, options.members)) { + allowedTypes = _.concat(allowedTypes, permissionsConfig.types); + } + }); + allowedTypes = _.uniq(allowedTypes); + + // only return Project Estimation Types which are allowed to the user + options.where.type = allowedTypes; // eslint-disable-line no-param-reassign + return callback ? callback(null) : null; + }, + }, + }, + ); + + /** + * Find all project estimation items for project + * + * TODO: this method can rewritten without using `models` + * and using JOIN instead for retrieving ProjectEstimationTimes by projectId + * + * @param {Object} models all models + * @param {Number} projectId project id + * @param {Object} [options] options + * + * @returns {Promise} list of project estimation items + */ + ProjectEstimationItem.findAllByProject = (models, projectId, options) => + models.ProjectEstimation.findAll({ + raw: true, + where: { + projectId, + }, + }).then((estimations) => { + const optionsCombined = _.assign({}, options); + // update where to always filter by projectEstimationsIds of the project + optionsCombined.where = _.assign({}, optionsCombined.where, { + projectEstimationId: _.map(estimations, 'id'), + }); + + return ProjectEstimationItem.findAll(optionsCombined); + }); + + /** + * Delete all project estimation items for project + * + * TODO: this method can rewritten without using `models` + * and using JOIN instead for retrieving ProjectEstimationTimes by projectId + * + * @param {Object} models all models + * @param {Number} projectId project id + * @param {Object} reqUser user who makes the request + * @param {Object} [options] options + * + * @returns {Promise} result of destroy query + */ + ProjectEstimationItem.deleteAllForProject = (models, projectId, reqUser, options) => + ProjectEstimationItem.findAllByProject(models, projectId, options) + .then((estimationItems) => { + const estimationItemsOptions = { + where: { + id: _.map(estimationItems, 'id'), + }, + }; + + return ProjectEstimationItem.update({ deletedBy: reqUser.userId }, estimationItemsOptions) + .then(() => ProjectEstimationItem.destroy(estimationItemsOptions)); + }); + + return ProjectEstimationItem; +}; diff --git a/src/models/projectPhase.js b/src/models/projectPhase.js index a74675cf..ef6e2920 100644 --- a/src/models/projectPhase.js +++ b/src/models/projectPhase.js @@ -40,6 +40,7 @@ module.exports = function defineProjectPhase(sequelize, DataTypes) { ProjectPhase.associate = (models) => { ProjectPhase.hasMany(models.PhaseProduct, { as: 'products', foreignKey: 'phaseId' }); + ProjectPhase.belongsToMany(models.WorkStream, { through: models.PhaseWorkStream, foreignKey: 'phaseId' }); }; /** @@ -53,22 +54,28 @@ module.exports = function defineProjectPhase(sequelize, DataTypes) { * @return {Object} the result rows and count */ ProjectPhase.search = async (parameters = {}, log) => { - let fieldsStr = _.map(parameters.fields, field => `project_phases."${field}"`); - fieldsStr = `${fieldsStr.join(',')}`; - const replacements = { - projectId: parameters.projectId, - }; - let dbQuery = `SELECT ${fieldsStr} FROM project_phases WHERE project_phases."projectId" = :projectId`; + // ordering + const orderBy = []; if (_.has(parameters, 'sortField') && _.has(parameters, 'sortType')) { - dbQuery = `${dbQuery} ORDER BY project_phases."${parameters.sortField}" ${parameters.sortType}`; + orderBy.push([parameters.sortField, parameters.sortType]); + } + // find options + const options = { + where: { + projectId: parameters.projectId, + }, + order: orderBy, + logging: (str) => { log.debug(str); }, + }; + // select fields + if (_.has(parameters, 'fields')) { + _.set(options, 'attributes', parameters.fields.filter(e => e !== 'products')); + if (parameters.fields.includes('products')) { + _.set(options, 'include', [{ model: this.sequelize.models.PhaseProduct, as: 'products' }]); + } } - return sequelize.query(dbQuery, - { type: sequelize.QueryTypes.SELECT, - logging: (str) => { log.debug(str); }, - replacements, - raw: true, - }) - .then(phases => ({ rows: phases, count: phases.length })); + + return ProjectPhase.findAll(options).then(phases => ({ rows: phases, count: phases.length })); }; return ProjectPhase; diff --git a/src/models/projectSetting.js b/src/models/projectSetting.js new file mode 100644 index 00000000..dd720264 --- /dev/null +++ b/src/models/projectSetting.js @@ -0,0 +1,115 @@ +/* eslint-disable valid-jsdoc */ +/** + * ProjectSetting model + * + * WARNING: This model contains sensitive data! + * + * - To return data from this model to the user always use methods `find`/`findAll` + * and provide to them `options.reqUser` and `options.members` to check which records could be returned + * based on the user roles and `readPermission` property of the records. + * - For internal usage you can use `options.includeAllProjectSettingsForInternalUsage` + * which would force `find`/`findAll` to return all the records without checking permissions. + * Use the data returned in such way ONLY FOR INTERNAL usage. It means such data can be used + * to make some calculations inside Project Service but it should be never returned to the user as it is. + */ + +import _ from 'lodash'; +import { VALUE_TYPE } from '../constants'; +import util from '../util'; + +module.exports = (sequelize, DataTypes) => { + const ProjectSetting = sequelize.define( + 'ProjectSetting', + { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + key: { type: DataTypes.STRING(255) }, + value: { type: DataTypes.STRING(255) }, + valueType: { + type: DataTypes.STRING, + validate: { + isIn: [_.values(VALUE_TYPE)], + }, + }, + projectId: { type: DataTypes.BIGINT, allowNull: false }, // Project id + metadata: { type: DataTypes.JSON, allowNull: false, defaultValue: {} }, + readPermission: { type: DataTypes.JSON, allowNull: false, defaultValue: {} }, + writePermission: { type: DataTypes.JSON, allowNull: false, defaultValue: {} }, + deletedAt: { type: DataTypes.DATE, allowNull: true }, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + deletedBy: DataTypes.INTEGER, + createdBy: { type: DataTypes.INTEGER, allowNull: false }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false }, + }, + { + tableName: 'project_settings', + paranoid: true, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + indexes: [ + { + unique: true, + fields: ['key', 'projectId'], + }, + ], + hooks: { + /** + * Inside before hook we are checking that required options are provided + * We do it in `beforeFind` instead of `afterFind` to avoid unnecessary data retrievement + * + * @param {Object} options find/findAll options + * @param {Function} callback callback after hook + */ + beforeFind: (options, callback) => { + // ONLY FOR INTERNAL USAGE: don't use this option to return the data by API + if (options.includeAllProjectSettingsForInternalUsage) { + return callback ? callback(null) : null; + } + + if (!options.reqUser || !options.members) { + const err = new Error('You must provide reqUser and project member to get project settings'); + if (!callback) throw err; + return callback(err); + } + + return callback ? callback(null) : null; + }, + + /** + * Inside after hook we are filtering records based on `readPermission` and user roles + * + * @param {Mixed} results one result from `find()` or array of results form `findAll()` + * @param {Object} options find/findAll options + * @param {Function} callback callback after hook + */ + afterFind: (results, options, callback) => { + // ONLY FOR INTERNAL USAGE: don't use this option to return the data by API + if (options.includeAllProjectSettingsForInternalUsage) { + return callback ? callback(null) : null; + } + + // if we have an array of results form `findAll()` we are filtering results + if (_.isArray(results)) { + // remove results from the "end" using `index` if user doesn't have permissions for to access them + for (let index = results.length - 1; index >= 0; index -= 1) { + if (!util.hasPermission(results[index].readPermission, options.reqUser, options.members)) { + results.splice(index, 1); + } + } + + // if we have one result from `find()` we check if user has permission for the record + } else if (results && !util.hasPermission(results.readPermission, options.reqUser, options.members)) { + const err = new Error('User doesn\'t have permission to access this record.'); + if (!callback) throw err; + return callback(err); + } + + return callback ? callback(null) : null; + }, + }, + }, + ); + + return ProjectSetting; +}; diff --git a/src/models/scopeChangeRequest.js b/src/models/scopeChangeRequest.js new file mode 100644 index 00000000..e527b0ca --- /dev/null +++ b/src/models/scopeChangeRequest.js @@ -0,0 +1,68 @@ +/* eslint-disable valid-jsdoc */ +import { SCOPE_CHANGE_REQ_STATUS } from '../constants'; + +/** + * The ScopeChangeRequest model + */ + +module.exports = (sequelize, DataTypes) => { + const ScopeChangeRequest = sequelize.define('ScopeChangeRequest', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + projectId: { type: DataTypes.BIGINT, allowNull: false }, + oldScope: { type: DataTypes.JSON, allowNull: false }, + newScope: { type: DataTypes.JSON, allowNull: false }, + status: { type: DataTypes.STRING(45), allowNull: false }, + + deletedAt: { type: DataTypes.DATE, allowNull: true }, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + approvedAt: { type: DataTypes.DATE, allowNull: true }, + deletedBy: { type: DataTypes.INTEGER, allowNull: true }, + createdBy: { type: DataTypes.INTEGER, allowNull: false }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false }, + approvedBy: { type: DataTypes.INTEGER, allowNull: true }, + }, { + tableName: 'scope_change_requests', + paranoid: true, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + deletedAt: 'deletedAt', + }); + + ScopeChangeRequest.findScopeChangeRequest = (projectId, { requestId, status }) => { + const where = { + projectId, + }; + if (status) { + where.status = status; + } + if (requestId) { + where.id = requestId; + } + return ScopeChangeRequest.findOne({ + where, + }); + }; + + ScopeChangeRequest.findPendingScopeChangeRequest = projectId => + ScopeChangeRequest.findScopeChangeRequest( + projectId, + { status: { $in: [SCOPE_CHANGE_REQ_STATUS.PENDING, SCOPE_CHANGE_REQ_STATUS.APPROVED] } }, + ); + + ScopeChangeRequest.getProjectScopeChangeRequests = (projectId, status) => { + const where = { + projectId, + }; + if (status) { + where.status = status; + } + return ScopeChangeRequest.findAll({ + where, + raw: true, + }); + }; + + return ScopeChangeRequest; +}; diff --git a/src/models/statusHistory.js b/src/models/statusHistory.js new file mode 100644 index 00000000..b73d3650 --- /dev/null +++ b/src/models/statusHistory.js @@ -0,0 +1,35 @@ +/* eslint-disable valid-jsdoc */ + +import _ from 'lodash'; +import { MILESTONE_STATUS } from '../constants'; + +module.exports = function defineStatusHistory(sequelize, DataTypes) { + const StatusHistory = sequelize.define('StatusHistory', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + reference: { type: DataTypes.STRING, allowNull: false }, + referenceId: { type: DataTypes.BIGINT, allowNull: false }, + status: { + type: DataTypes.STRING, + allowNull: false, + validate: { + isIn: [_.values(MILESTONE_STATUS)], + }, + }, + comment: DataTypes.TEXT, + createdBy: { type: DataTypes.INTEGER, allowNull: false }, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + }, { + tableName: 'status_history', + paranoid: false, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + deletedAt: 'deletedAt', + indexes: [], + classMethods: {}, + }); + + return StatusHistory; +}; diff --git a/src/models/versionModelClassMethods.js b/src/models/versionModelClassMethods.js index 13d79d70..bcf7c7fa 100644 --- a/src/models/versionModelClassMethods.js +++ b/src/models/versionModelClassMethods.js @@ -9,6 +9,17 @@ */ function versionModelClassMethods(model, jsonField) { return { + findOneWithLatestRevision(query) { + return model.findOne({ + where: { + key: query.key, + version: query.version, + }, + order: [['revision', 'DESC']], + limit: 1, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }); + }, deleteOldestRevision(userId, key, version) { return model.findOne({ where: { diff --git a/src/models/workManagementPermission.js b/src/models/workManagementPermission.js new file mode 100644 index 00000000..17915e69 --- /dev/null +++ b/src/models/workManagementPermission.js @@ -0,0 +1,35 @@ +/* eslint-disable valid-jsdoc */ + +/** + * The WorkManagementPermission model + */ +module.exports = (sequelize, DataTypes) => { + const WorkManagementPermission = sequelize.define('WorkManagementPermission', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + policy: { type: DataTypes.STRING(255), allowNull: false }, + permission: { type: DataTypes.JSON, allowNull: false }, + projectTemplateId: { type: DataTypes.BIGINT, allowNull: false }, + + deletedAt: { type: DataTypes.DATE, allowNull: true }, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + deletedBy: { type: DataTypes.INTEGER, allowNull: true }, + createdBy: { type: DataTypes.INTEGER, allowNull: false }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false }, + }, { + tableName: 'work_management_permissions', + paranoid: true, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + deletedAt: 'deletedAt', + indexes: [ + { + unique: true, + fields: ['policy', 'projectTemplateId'], + }, + ], + }); + + return WorkManagementPermission; +}; diff --git a/src/models/workStream.js b/src/models/workStream.js new file mode 100644 index 00000000..d43628b8 --- /dev/null +++ b/src/models/workStream.js @@ -0,0 +1,42 @@ +/* eslint-disable valid-jsdoc */ + +/** + * The WorkStream model + */ +import _ from 'lodash'; +import { WORKSTREAM_STATUS } from '../constants'; + +module.exports = (sequelize, DataTypes) => { + const WorkStream = sequelize.define('WorkStream', { + id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, + name: { type: DataTypes.STRING(255), allowNull: false }, + type: { type: DataTypes.STRING(45), allowNull: false }, + status: { + type: DataTypes.STRING, + allowNull: false, + validate: { + isIn: [_.values(WORKSTREAM_STATUS)], + }, + }, + deletedAt: { type: DataTypes.DATE, allowNull: true }, + createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + deletedBy: { type: DataTypes.INTEGER, allowNull: true }, + createdBy: { type: DataTypes.INTEGER, allowNull: false }, + updatedBy: { type: DataTypes.INTEGER, allowNull: false }, + }, { + tableName: 'work_streams', + paranoid: true, + timestamps: true, + updatedAt: 'updatedAt', + createdAt: 'createdAt', + deletedAt: 'deletedAt', + indexes: [], + }); + + WorkStream.associate = (models) => { + WorkStream.belongsToMany(models.ProjectPhase, { through: models.PhaseWorkStream, foreignKey: 'workStreamId' }); + }; + + return WorkStream; +}; diff --git a/src/permissions/constants.js b/src/permissions/constants.js new file mode 100644 index 00000000..570bc152 --- /dev/null +++ b/src/permissions/constants.js @@ -0,0 +1,39 @@ +/** + * Definitions of permissions which could be used with util methods + * `util.hasPermission` or `util.hasPermissionForProject`. + * + * We can define permission using two logics: + * 1. **WHAT** can be done with such a permission. Such constants may have names like: + * - `VIEW_PROJECT` + * - `EDIT_MILESTONE` + * - `DELETE_WORK` + * and os on. + * 2. **WHO** can do actions with such a permission. Such constants **MUST** start from the prefix `ROLES_`, examples: + * - `ROLES_COPILOT_AND_ABOVE` + * - `ROLES_PROJECT_MEMBERS` + * - `ROLES_ADMINS` + */ +import { + PROJECT_MEMBER_ROLE, + ADMIN_ROLES, +} from '../constants'; + +export const PERMISSION = { // eslint-disable-line import/prefer-default-export + /** + * Permissions defined by logic: **WHO** can do actions with such a permission. + */ + ROLES_COPILOT_AND_ABOVE: { + topcoderRoles: ADMIN_ROLES, + projectRoles: [ + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.COPILOT, + ], + }, + /** + * Permissions defined by logic: **WHAT** can be done with such a permission. + */ +}; + diff --git a/src/permissions/copilotAndAbove.js b/src/permissions/copilotAndAbove.js index e5d5121a..d6e8b21c 100644 --- a/src/permissions/copilotAndAbove.js +++ b/src/permissions/copilotAndAbove.js @@ -1,18 +1,31 @@ +import _ from 'lodash'; import util from '../util'; -import { MANAGER_ROLES, USER_ROLE } from '../constants'; - +import models from '../models'; +import { PERMISSION } from './constants'; /** - * Permission to alloow copilot and above roles to perform certain operations + * Permission to allow copilot and above roles to perform certain operations + * - User with Topcoder admins roles should be able to perform the operations. + * - Project members with copilot and manager Project roles should be also able to perform the operations. * @param {Object} req the express request instance * @return {Promise} returns a promise */ module.exports = req => new Promise((resolve, reject) => { - const hasAccess = util.hasRoles(req, [...MANAGER_ROLES, USER_ROLE.COPILOT]); + const projectId = _.parseInt(req.params.projectId); + + return models.ProjectMember.getActiveProjectMembers(projectId) + .then((members) => { + const hasPermission = util.hasPermission(PERMISSION.ROLES_COPILOT_AND_ABOVE, req.authUser, members); - if (!hasAccess) { - return reject(new Error('You do not have permissions to perform this action')); - } + // TODO should we really do this? + // if no, we can replace `getActiveProjectMembers + util.hasPermission` with one `util.hasPermissionForProject` + req.context = req.context || {}; + req.context.currentProjectMembers = members; - return resolve(true); + if (!hasPermission) { + // the copilot or manager is not a registered project member + return reject(new Error('You do not have permissions to perform this action')); + } + return resolve(true); + }); }); diff --git a/src/permissions/index.js b/src/permissions/index.js index 431816ce..33dd62a9 100644 --- a/src/permissions/index.js +++ b/src/permissions/index.js @@ -8,8 +8,10 @@ const projectMemberDelete = require('./projectMember.delete'); const projectAdmin = require('./admin.ops'); const projectAttachmentUpdate = require('./project.updateAttachment'); const projectAttachmentDownload = require('./project.downloadAttachment'); -// const connectManagerOrAdmin = require('./connectManagerOrAdmin.ops'); +const connectManagerOrAdmin = require('./connectManagerOrAdmin.ops'); const copilotAndAbove = require('./copilotAndAbove'); +const workManagementPermissions = require('./workManagementForTemplate'); +const projectSettingEdit = require('./projectSetting.edit'); module.exports = () => { Authorizer.setDeniedStatusCode(403); @@ -100,4 +102,40 @@ module.exports = () => { Authorizer.setPolicy('planConfig.edit', projectAdmin); Authorizer.setPolicy('planConfig.delete', projectAdmin); Authorizer.setPolicy('planConfig.view', true); // anyone can view price config + + // Work stream + Authorizer.setPolicy('workStream.create', projectAdmin); + Authorizer.setPolicy('workStream.edit', workManagementPermissions('workStream.edit')); + Authorizer.setPolicy('workStream.delete', projectAdmin); + Authorizer.setPolicy('workStream.view', projectView); + + // Work + Authorizer.setPolicy('work.create', workManagementPermissions('work.create')); + Authorizer.setPolicy('work.edit', workManagementPermissions('work.edit')); + Authorizer.setPolicy('work.delete', workManagementPermissions('work.delete')); + Authorizer.setPolicy('work.view', projectView); + + // Work item + Authorizer.setPolicy('workItem.create', workManagementPermissions('workItem.create')); + Authorizer.setPolicy('workItem.edit', workManagementPermissions('workItem.edit')); + Authorizer.setPolicy('workItem.delete', workManagementPermissions('workItem.delete')); + Authorizer.setPolicy('workItem.view', projectView); + + // Work management permission + Authorizer.setPolicy('workManagementPermission.create', projectAdmin); + Authorizer.setPolicy('workManagementPermission.edit', projectAdmin); + Authorizer.setPolicy('workManagementPermission.delete', projectAdmin); + Authorizer.setPolicy('workManagementPermission.view', projectAdmin); + + // Project Permissions + Authorizer.setPolicy('permissions.view', projectView); + + // Project Settings + Authorizer.setPolicy('projectSetting.create', connectManagerOrAdmin); + Authorizer.setPolicy('projectSetting.edit', projectSettingEdit); + Authorizer.setPolicy('projectSetting.delete', connectManagerOrAdmin); + Authorizer.setPolicy('projectSetting.view', projectView); + + // Project Estimation Items + Authorizer.setPolicy('projectEstimation.item.list', copilotAndAbove); }; diff --git a/src/permissions/project.delete.js b/src/permissions/project.delete.js index 9fe49fc8..1d0841ca 100644 --- a/src/permissions/project.delete.js +++ b/src/permissions/project.delete.js @@ -23,7 +23,13 @@ module.exports = freq => new Promise((resolve, reject) => { const hasAccess = util.hasAdminRole(req) || !_.isUndefined(_.find(members, m => m.userId === req.authUser.userId && ((m.role === PROJECT_MEMBER_ROLE.CUSTOMER && m.isPrimary) || - m.role === PROJECT_MEMBER_ROLE.MANAGER))); + [ + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + ].includes(m.role) + ))); if (!hasAccess) { // user is not an admin nor is a registered project member diff --git a/src/permissions/projectMember.delete.js b/src/permissions/projectMember.delete.js index 0f0ec42c..5f4bb946 100644 --- a/src/permissions/projectMember.delete.js +++ b/src/permissions/projectMember.delete.js @@ -25,7 +25,12 @@ module.exports = freq => new Promise((resolve, reject) => { const memberToBeRemoved = _.find(members, m => m.id === prjMemberId); // check if auth user has acecss to this project const hasAccess = util.hasAdminRole(req) - || (authMember && memberToBeRemoved && (authMember.role === PROJECT_MEMBER_ROLE.MANAGER || + || (authMember && memberToBeRemoved && ([ + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + ].includes(authMember.role) || (authMember.role === PROJECT_MEMBER_ROLE.CUSTOMER && authMember.isPrimary && memberToBeRemoved.role === PROJECT_MEMBER_ROLE.CUSTOMER) || memberToBeRemoved.userId === req.authUser.userId)); diff --git a/src/permissions/projectSetting.edit.js b/src/permissions/projectSetting.edit.js new file mode 100644 index 00000000..0d9794c5 --- /dev/null +++ b/src/permissions/projectSetting.edit.js @@ -0,0 +1,40 @@ +import util from '../util'; +import models from '../models'; + +/** + * Only users who have "writePermission" of ProjectSetting can edit this entity. + * @param {Object} freq the express request instance + * @return {Promise} Returns a promise + */ +module.exports = freq => new Promise((resolve, reject) => { + const projectId = freq.params.projectId; + const settingId = freq.params.id; + let projectMembers = []; + + return models.ProjectMember.getActiveProjectMembers(projectId) + .then((members) => { + const req = freq; + req.context = req.context || {}; + req.context.currentProjectMembers = members; + + projectMembers = members; + return Promise.resolve(); + }) + .then(() => models.ProjectSetting.findOne({ + where: { projectId, id: settingId }, + raw: true, + includeAllProjectSettingsForInternalUsage: true, + }).then((setting) => { + if (!setting) { + // let route handle this 404 error. + return resolve(true); + } + const hasAccess = util.hasPermission(setting.writePermission, freq.authUser, projectMembers); + if (!hasAccess) { + const errorMessage = 'You do not have permissions to perform this action'; + // user is not an admin nor is a registered project member + return reject(new Error(errorMessage)); + } + return resolve(true); + })); +}); diff --git a/src/permissions/workManagementForTemplate.js b/src/permissions/workManagementForTemplate.js new file mode 100644 index 00000000..12c97c30 --- /dev/null +++ b/src/permissions/workManagementForTemplate.js @@ -0,0 +1,56 @@ + +import _ from 'lodash'; +import util from '../util'; +import models from '../models'; +import { MANAGER_ROLES } from '../constants'; + +/** + * Based on allowRule and denyRule allow/deny users to execute the policy + * @param {String} policy the work management permission policy + * @return {Promise} Returns a promise + */ +module.exports = policy => req => new Promise((resolve, reject) => { + const projectId = _.parseInt(req.params.projectId); + + return models.Project.findOne({ + where: { + id: projectId, + }, + }) + .then((project) => { + if (!project) { + const apiErr = new Error(`Project not found for id ${projectId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + if (!project.templateId) { + return null; + } + + return models.WorkManagementPermission.findOne({ + where: { + policy, + projectTemplateId: project.templateId, + }, + }); + }) + .then((workManagementPermission) => { + if (!workManagementPermission) { + // TODO REMOVE THIS!!! + // TEMPORARY let all the Topcoder managers to do all the work management + // if there are no permission records in the DB for the template + return util.hasPermission({ topcoderRoles: MANAGER_ROLES }, req.authUser); + // return false; + } + + return util.hasPermissionForProject(workManagementPermission.permission, req.authUser, projectId); + }) + .then((hasAccess) => { + if (!hasAccess) { + const errorMessage = 'You do not have permissions to perform this action'; + return reject(new Error(errorMessage)); + } + return resolve(true); + }); +}); diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index c594713c..f46a4a97 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -109,7 +109,7 @@ module.exports = [ }).then((_newAttachment) => { newAttachment = _newAttachment.get({ plain: true }); req.log.debug('New Attachment record: ', newAttachment); - if (process.env.NODE_ENV !== 'development') { + 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`, { @@ -118,7 +118,7 @@ module.exports = [ } return Promise.resolve(); }).then((resp) => { - if (process.env.NODE_ENV !== 'development') { + 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) { diff --git a/src/routes/attachments/delete.js b/src/routes/attachments/delete.js index e96050f3..e63f095e 100644 --- a/src/routes/attachments/delete.js +++ b/src/routes/attachments/delete.js @@ -5,6 +5,7 @@ import _ from 'lodash'; import { middleware as tcMiddleware, } from 'tc-core-library-js'; +import config from 'config'; import models from '../../models'; import util from '../../util'; import fileService from '../../services/fileService'; @@ -42,7 +43,7 @@ module.exports = [ .then(() => _attachment.destroy()); })) .then((_attachment) => { - if (process.env.NODE_ENV !== 'development') { + if (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true') { return fileService.deleteFile(req, _attachment.filePath); } return Promise.resolve(); diff --git a/src/routes/attachments/download.js b/src/routes/attachments/download.js index b3815e51..d925774a 100644 --- a/src/routes/attachments/download.js +++ b/src/routes/attachments/download.js @@ -61,6 +61,9 @@ 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); }) @@ -76,6 +79,7 @@ module.exports = [ return getFileDownloadUrl(req, attachment.filePath); }) .then((result) => { + req.log.debug('getFileDownloadUrl result: ', JSON.stringify(result)); const url = result[1]; return res.json({ url }); }) diff --git a/src/routes/form/version/getVersion.js b/src/routes/form/version/getVersion.js index 345f2044..b8a49431 100644 --- a/src/routes/form/version/getVersion.js +++ b/src/routes/form/version/getVersion.js @@ -4,8 +4,10 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import models from '../../../models'; + import util from '../../../util'; +import models from '../../../models'; + const permissions = tcMiddleware.permissions; diff --git a/src/routes/index.js b/src/routes/index.js index adb166b5..c57e6651 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -65,6 +65,11 @@ router.route('/v5/projects/metadata/productCategories') router.route('/v5/projects/metadata/productCategories/:key') .get(require('./productCategories/get')); +router.route('/v5/projects/metadata/workManagementPermission') + .get(require('./workManagementPermissions/list')); +router.route('/v5/projects/metadata/workManagementPermission/:id') + .get(require('./workManagementPermissions/get')); + router.use('/v5/projects/metadata', compression()); router.route('/v5/projects/metadata') @@ -90,6 +95,14 @@ router.route('/v5/projects/:projectId(\\d+)') .patch(require('./projects/update')) .delete(require('./projects/delete')); +router.route('/v5/projects/:projectId(\\d+)/scopeChangeRequests') + .post(require('./scopeChangeRequests/create')); + // .get(require('./scopeChangeRequests/list')); +router.route('/v5/projects/:projectId(\\d+)/scopeChangeRequests/:requestId(\\d+)') + // .get(require('./scopeChangeRequests/get')) + .patch(require('./scopeChangeRequests/update')); + // .delete(require('./scopeChangeRequests/delete')); + router.route('/v5/projects/:projectId(\\d+)/members') .get(require('./projectMembers/list')) .post(require('./projectMembers/create')); @@ -99,6 +112,10 @@ router.route('/v5/projects/:projectId(\\d+)/members/:id(\\d+)') .delete(require('./projectMembers/delete')) .patch(require('./projectMembers/update')); +// Permissions +router.route('/v5/projects/:projectId(\\d+)/permissions') + .get(require('./permissions/get')); + router.route('/v5/projects/:projectId(\\d+)/attachments') .post(require('./attachments/create')) .get(require('./attachments/list')); @@ -150,6 +167,13 @@ router.route('/v5/projects/metadata/productCategories/:key') .patch(require('./productCategories/update')) .delete(require('./productCategories/delete')); +router.route('/v5/projects/metadata/workManagementPermission') + .post(require('./workManagementPermissions/create')); + +router.route('/v5/projects/metadata/workManagementPermission/:id') + .patch(require('./workManagementPermissions/update')) + .delete(require('./workManagementPermissions/delete')); + router.route('/v5/projects/metadata/projectTypes') .post(require('./projectTypes/create')); @@ -264,6 +288,52 @@ router.route('/v5/projects/metadata/planConfig/:key/versions/:version(\\d+)') .patch(require('./planConfig/version/update')) .delete(require('./planConfig/version/delete')); +// work streams +router.route('/v5/projects/:projectId(\\d+)/workstreams') + .get(require('./workStreams/list')) + .post(require('./workStreams/create')); + +router.route('/v5/projects/:projectId(\\d+)/workstreams/:id(\\d+)') + .get(require('./workStreams/get')) + .patch(require('./workStreams/update')) + .delete(require('./workStreams/delete')); + +// works +router.route('/v5/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works') + .get(require('./works/list')) + .post(require('./works/create')); + +router.route('/v5/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works/:id(\\d+)') + .get(require('./works/get')) + .patch(require('./works/update')) + .delete(require('./works/delete')); + +// work items +router.route('/v5/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works/:workId(\\d+)/workitems') + .get(require('./workItems/list')) + .post(require('./workItems/create')); + +router.route('/v5/projects/:projectId(\\d+)/workstreams/:workStreamId(\\d+)/works/:workId(\\d+)/workitems/:id(\\d+)') + .get(require('./workItems/get')) + .patch(require('./workItems/update')) + .delete(require('./workItems/delete')); + +router.route('/v5/projects/:projectId/reports') + .get(require('./projectReports/getReport')); + +// Project Settings +router.route('/v5/projects/:projectId(\\d+)/settings/:id(\\d+)') + .patch(require('./projectSettings/update')) + .delete(require('./projectSettings/delete')); + +router.route('/v5/projects/:projectId(\\d+)/settings') + .get(require('./projectSettings/list')) + .post(require('./projectSettings/create')); + +// Project Estimation Items +router.route('/v5/projects/:projectId(\\d+)/estimations/:estimationId(\\d+)/items') + .get(require('./projectEstimationItems/list')); + // register error handler router.use((err, req, res, next) => { // eslint-disable-line no-unused-vars // DO NOT REMOVE next arg.. even though eslint diff --git a/src/routes/metadata/list.js b/src/routes/metadata/list.js index d2b239c2..e254b979 100644 --- a/src/routes/metadata/list.js +++ b/src/routes/metadata/list.js @@ -26,6 +26,10 @@ function getUsedModel() { priceConfig: { }, }; const query = { + where: { + deletedAt: { $eq: null }, + disabled: false, + }, attributes: { exclude: ['deletedAt', 'deletedBy'] }, raw: true, }; @@ -73,6 +77,14 @@ module.exports = [ attributes: { exclude: ['deletedAt', 'deletedBy'] }, raw: true, }; + const projectProductTemplateQuery = { + where: { + deletedAt: { $eq: null }, + disabled: false, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }; // when user query with includeAllReferred, return result with all used version of // Form, PriceConfig, PlanConfig @@ -92,8 +104,8 @@ module.exports = [ }).then((latestVersionModels) => { latestVersion = latestVersionModels; return Promise.all([ - models.ProjectTemplate.findAll(query), - models.ProductTemplate.findAll(query), + models.ProjectTemplate.findAll(projectProductTemplateQuery), + models.ProductTemplate.findAll(projectProductTemplateQuery), models.MilestoneTemplate.findAll(query), models.ProjectType.findAll(query), models.ProductCategory.findAll(query), @@ -116,14 +128,15 @@ module.exports = [ .catch(next); } return Promise.all([ - models.ProjectTemplate.findAll(query), - models.ProductTemplate.findAll(query), + models.ProjectTemplate.findAll(projectProductTemplateQuery), + models.ProductTemplate.findAll(projectProductTemplateQuery), models.MilestoneTemplate.findAll(query), models.ProjectType.findAll(query), models.ProductCategory.findAll(query), models.Form.latestVersion(), models.PriceConfig.latestVersion(), models.PlanConfig.latestVersion(), + models.BuildingBlock.findAll(query), ]) .then((results) => { res.json({ @@ -135,6 +148,7 @@ module.exports = [ forms: results[5], priceConfigs: results[6], planConfigs: results[7], + buildingBlocks: results[8], }); }) .catch(next); diff --git a/src/routes/metadata/list.spec.js b/src/routes/metadata/list.spec.js index 762758e4..f9ae92ec 100644 --- a/src/routes/metadata/list.spec.js +++ b/src/routes/metadata/list.spec.js @@ -26,6 +26,7 @@ const projectTemplates = [ priceConfig: { key: 'key1', version: 1 }, createdBy: 1, updatedBy: 1, + disabled: false, }, ]; const productTemplates = [ @@ -178,6 +179,31 @@ const planConfigs = [ }, ]; +const buildingBlocks = [ + { + key: 'key1', + config: { + hello: 'world', + }, + privateConfig: { + message: 'you should not see this', + }, + createdBy: 1, + updatedBy: 1, + }, + { + key: 'key2', + config: { + hello: 'topcoder', + }, + privateConfig: { + message: 'you should not see this', + }, + createdBy: 1, + updatedBy: 1, + }, +]; + describe('GET all metadata', () => { before((done) => { testUtil.clearDb() @@ -188,7 +214,8 @@ describe('GET all metadata', () => { .then(() => models.ProductCategory.bulkCreate(productCategories)) .then(() => models.Form.bulkCreate(forms)) .then(() => models.PriceConfig.bulkCreate(priceConfigs)) - .then(() => models.PlanConfig.bulkCreate(planConfigs).then(() => done())); + .then(() => models.PlanConfig.bulkCreate(planConfigs)) + .then(() => models.BuildingBlock.bulkCreate(buildingBlocks).then(() => done())); }); after((done) => { @@ -288,5 +315,29 @@ describe('GET all metadata', () => { }) .expect(200, done); }); + + it('should return correct building blocks for admin', (done) => { + request(server) + .get('/v5/projects/metadata') + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + should.exist(resJson.buildingBlocks); + resJson.buildingBlocks.length.should.be.eql(2); + resJson.buildingBlocks[0].key.should.be.eql('key1'); + should.not.exist(resJson.buildingBlocks[0].privateConfig); + resJson.buildingBlocks[1].key.should.be.eql('key2'); + should.not.exist(resJson.buildingBlocks[1].privateConfig); + done(); + } + }); + }); }); }); diff --git a/src/routes/milestoneTemplates/clone.js b/src/routes/milestoneTemplates/clone.js index d6c21f96..26662879 100644 --- a/src/routes/milestoneTemplates/clone.js +++ b/src/routes/milestoneTemplates/clone.js @@ -28,7 +28,7 @@ module.exports = [ (req, res, next) => { let result; - return models.sequelize.transaction(tx => // eslint-disable-line no-unused-vars + return models.sequelize.transaction(() => // Find the product template models.MilestoneTemplate.findAll({ where: { diff --git a/src/routes/milestoneTemplates/create.js b/src/routes/milestoneTemplates/create.js index e2fc5957..d72f727a 100644 --- a/src/routes/milestoneTemplates/create.js +++ b/src/routes/milestoneTemplates/create.js @@ -48,9 +48,9 @@ module.exports = [ updatedBy: req.authUser.userId, }); let result; - return models.sequelize.transaction(tx => + return models.sequelize.transaction(() => // Create the milestone template - models.MilestoneTemplate.create(entity, { transaction: tx }) + models.MilestoneTemplate.create(entity) .then((createdEntity) => { // Omit deletedAt and deletedBy result = _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'); @@ -64,7 +64,6 @@ module.exports = [ id: { $ne: result.id }, order: { $gte: result.order }, }, - transaction: tx, }); }) .then((updatedCount) => { @@ -77,7 +76,6 @@ module.exports = [ }, order: [['updatedAt', 'DESC']], limit: updatedCount[0], - transaction: tx, }); } return Promise.resolve(); diff --git a/src/routes/milestones/create.js b/src/routes/milestones/create.js index 12848879..b297411b 100644 --- a/src/routes/milestones/create.js +++ b/src/routes/milestones/create.js @@ -30,10 +30,10 @@ const schema = { type: Joi.string().max(45).required(), details: Joi.object(), order: Joi.number().integer().required(), - plannedText: Joi.string().max(512).required(), - activeText: Joi.string().max(512).required(), - completedText: Joi.string().max(512).required(), - blockedText: Joi.string().max(512).required(), + plannedText: Joi.string().max(512), + activeText: Joi.string().max(512), + completedText: Joi.string().max(512), + blockedText: Joi.string().max(512), hidden: Joi.boolean().optional(), createdAt: Joi.any().strip(), updatedAt: Joi.any().strip(), @@ -62,8 +62,6 @@ module.exports = [ let error; if (req.body.startDate < req.timeline.startDate) { error = 'Milestone startDate must not be before the timeline startDate'; - } else if (req.body.endDate && req.timeline.endDate && req.body.endDate > req.timeline.endDate) { - error = 'Milestone endDate must not be after the timeline endDate'; } if (error) { const apiErr = new Error(error); @@ -71,9 +69,9 @@ module.exports = [ return next(apiErr); } - return models.sequelize.transaction(tx => + return models.sequelize.transaction(() => // Save to DB - models.Milestone.create(entity, { transaction: tx }) + models.Milestone.create(entity) .then((createdEntity) => { // Omit deletedAt, deletedBy result = _.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy'); @@ -86,7 +84,6 @@ module.exports = [ id: { $ne: result.id }, order: { $gte: result.order }, }, - transaction: tx, }); }) .then((updatedCount) => { @@ -99,7 +96,6 @@ module.exports = [ }, order: [['updatedAt', 'DESC']], limit: updatedCount[0], - transaction: tx, }); } return Promise.resolve(); diff --git a/src/routes/milestones/create.spec.js b/src/routes/milestones/create.spec.js index 8d9163b1..bcd0e7f2 100644 --- a/src/routes/milestones/create.spec.js +++ b/src/routes/milestones/create.spec.js @@ -146,7 +146,7 @@ describe('CREATE milestone', () => { name: 'milestone 1', duration: 2, startDate: '2018-05-03T00:00:00.000Z', - status: 'open', + status: 'draft', type: 'type1', details: { detail1: { @@ -168,7 +168,7 @@ describe('CREATE milestone', () => { name: 'milestone 2', duration: 3, startDate: '2018-05-04T00:00:00.000Z', - status: 'open', + status: 'draft', type: 'type2', order: 2, plannedText: 'plannedText 2', @@ -183,7 +183,7 @@ describe('CREATE milestone', () => { name: 'milestone 3', duration: 4, startDate: '2018-05-04T00:00:00.000Z', - status: 'open', + status: 'draft', type: 'type3', order: 3, plannedText: 'plannedText 3', @@ -212,7 +212,7 @@ describe('CREATE milestone', () => { startDate: '2018-05-05T00:00:00.000Z', endDate: '2018-05-07T00:00:00.000Z', completionDate: '2018-05-08T00:00:00.000Z', - status: 'open', + status: 'draft', type: 'type4', details: { detail1: { @@ -309,66 +309,6 @@ describe('CREATE milestone', () => { .expect(400, done); }); - it('should return 400 if missing plannedText', (done) => { - const invalidBody = _.assign({}, body, { - plannedText: undefined, - }); - - request(server) - .post('/v5/timelines/1/milestones') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send(invalidBody) - .expect('Content-Type', /json/) - .expect(400, done); - }); - - it('should return 400 if missing activeText', (done) => { - const invalidBody = _.assign({}, body, { - activeText: undefined, - }); - - request(server) - .post('/v5/timelines/1/milestones') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send(invalidBody) - .expect('Content-Type', /json/) - .expect(400, done); - }); - - it('should return 400 if missing completedText', (done) => { - const invalidBody = _.assign({}, body, { - completedText: undefined, - }); - - request(server) - .post('/v5/timelines/1/milestones') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send(invalidBody) - .expect('Content-Type', /json/) - .expect(400, done); - }); - - it('should return 400 if missing blockedText', (done) => { - const invalidBody = _.assign({}, body, { - blockedText: undefined, - }); - - request(server) - .post('/v5/timelines/1/milestones') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send(invalidBody) - .expect('Content-Type', /json/) - .expect(400, done); - }); - it('should return 400 if startDate is after endDate', (done) => { const invalidBody = _.assign({}, body, { startDate: '2018-05-29T00:00:00.000Z', @@ -416,21 +356,6 @@ describe('CREATE milestone', () => { .expect(400, done); }); - it('should return 400 if endDate is after the timeline endDate', (done) => { - const invalidBody = _.assign({}, body, { - endDate: '2018-06-13T00:00:00.000Z', - }); - - request(server) - .post('/v5/timelines/1/milestones') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send(invalidBody) - .expect('Content-Type', /json/) - .expect(400, done); - }); - it('should return 400 if invalid timelineId param', (done) => { request(server) .post('/v5/timelines/0/milestones') diff --git a/src/routes/milestones/delete.js b/src/routes/milestones/delete.js index 78b9a3e5..620b24ed 100644 --- a/src/routes/milestones/delete.js +++ b/src/routes/milestones/delete.js @@ -30,11 +30,10 @@ module.exports = [ id: req.params.milestoneId, }; - return models.sequelize.transaction(tx => + return models.sequelize.transaction(() => // Find the milestone models.Milestone.findOne({ where, - transaction: tx, }) .then((milestone) => { // Not found @@ -45,8 +44,8 @@ module.exports = [ } // Update the deletedBy, and soft delete - return milestone.update({ deletedBy: req.authUser.userId }, { transaction: tx }) - .then(() => milestone.destroy({ transaction: tx })); + return milestone.update({ deletedBy: req.authUser.userId }) + .then(() => milestone.destroy()); }) .then((deleted) => { // Send event to bus diff --git a/src/routes/milestones/list.spec.js b/src/routes/milestones/list.spec.js index 570e426f..e5ccab4a 100644 --- a/src/routes/milestones/list.spec.js +++ b/src/routes/milestones/list.spec.js @@ -3,8 +3,9 @@ */ import chai from 'chai'; import request from 'supertest'; -import sleep from 'sleep'; +// import sleep from 'sleep'; import config from 'config'; +import _ from 'lodash'; import models from '../../models'; import server from '../../app'; @@ -50,6 +51,7 @@ const milestones = [ detail2: [1, 2, 3], }, order: 1, + hidden: false, plannedText: 'plannedText 1', activeText: 'activeText 1', completedText: 'completedText 1', @@ -68,6 +70,7 @@ const milestones = [ status: 'open', type: 'type2', order: 2, + hidden: false, plannedText: 'plannedText 2', activeText: 'activeText 2', completedText: 'completedText 2', @@ -162,25 +165,24 @@ describe('LIST milestones', () => { updatedBy: 2, }, ])) - .then(() => - // Create timelines and milestones - models.Timeline.bulkCreate(timelines) - .then(() => models.Milestone.bulkCreate(milestones))) - .then(() => { + // Create timelines and milestones + .then(() => models.Timeline.bulkCreate(timelines)) + .then(() => models.Milestone.bulkCreate(milestones)) + .then((createdMilestones) => { // Index to ES - timelines[0].milestones = milestones; + timelines[0].milestones = _.map(createdMilestones, cm => _.omit(cm.toJSON(), 'deletedAt', 'deletedBy')); timelines[0].projectId = 1; return server.services.es.index({ index: ES_TIMELINE_INDEX, type: ES_TIMELINE_TYPE, id: timelines[0].id, body: timelines[0], - }) - .then(() => { - // sleep for some time, let elasticsearch indices be settled - sleep.sleep(5); - done(); - }); + }); + }) + .then(() => { + // sleep for some time, let elasticsearch indices be settled + // sleep.sleep(5); + done(); }); }); }); @@ -244,8 +246,18 @@ describe('LIST milestones', () => { const resJson = res.body; resJson.should.have.length(2); - resJson[0].should.be.eql(milestones[0]); - resJson[1].should.be.eql(milestones[1]); + resJson.forEach((milestone, index) => { + milestone.statusHistory.should.be.an('array'); + milestone.statusHistory.length.should.be.eql(1); + milestone.statusHistory.forEach((statusHistory) => { + statusHistory.reference.should.be.eql('milestone'); + statusHistory.referenceId.should.be.eql(milestone.id); + }); + + const m = _.omitBy(_.omit(milestone, ['statusHistory']), _.isNil); + + m.should.be.eql(milestones[index]); + }); done(); }); @@ -320,8 +332,10 @@ describe('LIST milestones', () => { const resJson = res.body; resJson.should.have.length(2); - resJson[0].should.be.eql(milestones[1]); - resJson[1].should.be.eql(milestones[0]); + const m1 = _.omitBy(_.omit(resJson[0], ['statusHistory']), _.isNil); + const m2 = _.omitBy(_.omit(resJson[1], ['statusHistory']), _.isNil); + m1.should.be.eql(milestones[1]); + m2.should.be.eql(milestones[0]); done(); }); diff --git a/src/routes/milestones/update.js b/src/routes/milestones/update.js index 6ed6b47c..bb7d48cd 100644 --- a/src/routes/milestones/update.js +++ b/src/routes/milestones/update.js @@ -4,12 +4,13 @@ import validate from 'express-validation'; import _ from 'lodash'; import moment from 'moment'; +import config from 'config'; import Joi from 'joi'; import Sequelize from 'sequelize'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; import validateTimeline from '../../middlewares/validateTimeline'; -import { EVENT, RESOURCES, MILESTONE_STATUS } from '../../constants'; +import { EVENT, RESOURCES, MILESTONE_STATUS, ADMIN_ROLES } from '../../constants'; import models from '../../models'; const permissions = tcMiddleware.permissions; @@ -110,6 +111,7 @@ const schema = { completedText: Joi.string().max(512).optional(), blockedText: Joi.string().max(512).optional(), hidden: Joi.boolean().optional(), + statusComment: Joi.string().when('status', { is: 'paused', then: Joi.required(), otherwise: Joi.optional() }), createdAt: Joi.any().strip(), updatedAt: Joi.any().strip(), deletedAt: Joi.any().strip(), @@ -144,13 +146,50 @@ module.exports = [ return models.sequelize.transaction(() => // Find the milestone models.Milestone.findOne({ where }) - .then((milestone) => { + .then(async (milestone) => { // Not found if (!milestone) { const apiErr = new Error(`Milestone not found for milestone id ${req.params.milestoneId}`); apiErr.status = 404; return Promise.reject(apiErr); } + const validStatuses = JSON.parse(config.get('VALID_STATUSES_BEFORE_PAUSED')); + if (entityToUpdate.status === MILESTONE_STATUS.PAUSED && !validStatuses.includes(milestone.status)) { + const validStatutesStr = validStatuses.join(', '); + const apiErr = new Error(`Milestone can only be paused from the next statuses: ${validStatutesStr}`); + apiErr.status = 400; + return Promise.reject(apiErr); + } + + if (entityToUpdate.status === 'resume') { + if (milestone.status !== MILESTONE_STATUS.PAUSED) { + const apiErr = new Error('Milestone status isn\'t paused'); + apiErr.status = 400; + return Promise.reject(apiErr); + } + const statusHistory = await models.StatusHistory.findAll({ + where: { referenceId: milestone.id }, + order: [['createdAt', 'desc'], ['id', 'desc']], + attributes: ['status', 'id'], + limit: 2, + raw: true, + }); + if (statusHistory.length === 2) { + entityToUpdate.status = statusHistory[1].status; + } else { + const apiErr = new Error('No previous status is found'); + apiErr.status = 500; + return Promise.reject(apiErr); + } + } + + if (entityToUpdate.completionDate || entityToUpdate.actualStartDate) { + if (!util.hasPermission({ topcoderRoles: ADMIN_ROLES }, req.authUser)) { + const apiErr = new Error('You are not authorised to perform this action'); + apiErr.status = 403; + return Promise.reject(apiErr); + } + } if (entityToUpdate.completionDate && entityToUpdate.completionDate < milestone.startDate) { const apiErr = new Error('The milestone completionDate should be greater or equal than the startDate.'); @@ -205,7 +244,7 @@ module.exports = [ } // Update - return milestone.update(entityToUpdate); + return milestone.update(entityToUpdate, { comment: entityToUpdate.statusComment }); }) .then((updatedMilestone) => { // Omit deletedAt, deletedBy diff --git a/src/routes/milestones/update.spec.js b/src/routes/milestones/update.spec.js index 056e884d..74b5a36e 100644 --- a/src/routes/milestones/update.spec.js +++ b/src/routes/milestones/update.spec.js @@ -141,7 +141,7 @@ describe('UPDATE Milestone', () => { startDate: '2018-05-13T00:00:00.000Z', endDate: '2018-05-14T00:00:00.000Z', completionDate: '2018-05-15T00:00:00.000Z', - status: 'open', + status: 'active', type: 'type1', details: { detail1: { @@ -166,7 +166,7 @@ describe('UPDATE Milestone', () => { name: 'Milestone 2', duration: 3, startDate: '2018-05-14T00:00:00.000Z', - status: 'open', + status: 'reviewed', type: 'type2', order: 2, plannedText: 'plannedText 2', @@ -184,7 +184,7 @@ describe('UPDATE Milestone', () => { name: 'Milestone 3', duration: 3, startDate: '2018-05-14T00:00:00.000Z', - status: 'open', + status: 'active', type: 'type3', order: 3, plannedText: 'plannedText 3', @@ -202,7 +202,7 @@ describe('UPDATE Milestone', () => { name: 'Milestone 4', duration: 3, startDate: '2018-05-14T00:00:00.000Z', - status: 'open', + status: 'active', type: 'type4', order: 4, plannedText: 'plannedText 4', @@ -220,7 +220,7 @@ describe('UPDATE Milestone', () => { name: 'Milestone 5', duration: 3, startDate: '2018-05-14T00:00:00.000Z', - status: 'open', + status: 'active', type: 'type5', order: 5, plannedText: 'plannedText 5', @@ -239,7 +239,7 @@ describe('UPDATE Milestone', () => { name: 'Milestone 6', duration: 3, startDate: '2018-05-14T00:00:00.000Z', - status: 'open', + status: 'active', type: 'type5', order: 1, plannedText: 'plannedText 6', @@ -267,7 +267,7 @@ describe('UPDATE Milestone', () => { duration: 3, completionDate: '2018-05-16T00:00:00.000Z', description: 'description-updated', - status: 'closed', + status: 'draft', type: 'type1-updated', details: { detail1: { @@ -302,6 +302,30 @@ describe('UPDATE Milestone', () => { .expect(403, done); }); + it('should return 403 for non-admin member updating the completionDate', (done) => { + const newBody = _.cloneDeep(body); + newBody.completionDate = '2019-01-16T00:00:00.000Z'; + request(server) + .patch('/v5/timelines/1/milestones/1') + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(newBody) + .expect(403, done); + }); + + it('should return 403 for non-admin member updating the actualStartDate', (done) => { + const newBody = _.cloneDeep(body); + newBody.actualStartDate = '2018-05-15T00:00:00.000Z'; + request(server) + .patch('/v5/timelines/1/milestones/1') + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(newBody) + .expect(403, done); + }); + it('should return 404 for non-existed timeline', (done) => { request(server) .patch('/v5/timelines/1234/milestones/1') @@ -711,7 +735,7 @@ describe('UPDATE Milestone', () => { name: 'Milestone 7', duration: 3, startDate: '2018-05-14T00:00:00.000Z', - status: 'open', + status: 'active', type: 'type7', order: 3, plannedText: 'plannedText 7', @@ -729,7 +753,7 @@ describe('UPDATE Milestone', () => { name: 'Milestone 8', duration: 3, startDate: '2018-05-14T00:00:00.000Z', - status: 'open', + status: 'active', type: 'type7', order: 4, plannedText: 'plannedText 8', @@ -784,7 +808,7 @@ describe('UPDATE Milestone', () => { name: 'Milestone 7', duration: 3, startDate: '2018-05-14T00:00:00.000Z', - status: 'open', + status: 'active', type: 'type7', order: 2, plannedText: 'plannedText 7', @@ -802,7 +826,7 @@ describe('UPDATE Milestone', () => { name: 'Milestone 8', duration: 3, startDate: '2018-05-14T00:00:00.000Z', - status: 'open', + status: 'active', type: 'type7', order: 4, plannedText: 'plannedText 8', @@ -1050,39 +1074,203 @@ describe('UPDATE Milestone', () => { .end(done); }); - it('should return 200 for connect manager', (done) => { + it('should return 200 for admin updating the completionDate', (done) => { + const newBody = _.cloneDeep(body); + newBody.completionDate = '2018-05-16T00:00:00.000Z'; + request(server) + .patch('/v5/timelines/1/milestones/1') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newBody) + .expect(200, done); + }); + + it('should return 200 for admin updating the actualStartDate', (done) => { + const newBody = _.cloneDeep(body); + newBody.actualStartDate = '2018-05-15T00:00:00.000Z'; + request(server) + .patch('/v5/timelines/1/milestones/1') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newBody) + .expect(200, done); + }); + + it('should return 403 for connect manager when entity to update has completionDate', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send(body) - .expect(200) + .expect(403) .end(done); }); - it('should return 200 for copilot', (done) => { + it('should return 403 for copilot when entity to update has completionDate', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send(body) - .expect(200) + .expect(403) .end(done); }); - it('should return 200 for member', (done) => { + it('should return 403 for member when entity to update has completionDate', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(body) - .expect(200) + .expect(403) .end(done); }); + it('should return 400 if try to pause and statusComment is missed', (done) => { + const newBody = _.cloneDeep(body); + newBody.status = 'paused'; + request(server) + .patch('/v5/timelines/1/milestones/1') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newBody) + .expect(400, done); + }); + + it('should return 400 if try to pause not active milestone', (done) => { + const newBody = _.cloneDeep(body); + newBody.status = 'paused'; + newBody.statusComment = 'milestone paused'; + request(server) + .patch('/v5/timelines/1/milestones/2') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newBody) + .expect(400, done); + }); + + it('should return 200 if try to pause and should have one status history created', (done) => { + const newBody = _.cloneDeep(body); + newBody.status = 'paused'; + newBody.statusComment = 'milestone paused'; + request(server) + .patch('/v5/timelines/1/milestones/1') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newBody) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + models.Milestone.findByPk(1).then((milestone) => { + milestone.status.should.be.eql('paused'); + return models.StatusHistory.findAll({ + where: { + reference: 'milestone', + referenceId: milestone.id, + status: milestone.status, + comment: 'milestone paused', + }, + paranoid: false, + }).then((statusHistories) => { + statusHistories.length.should.be.eql(1); + done(); + }); + }); + } + }); + }); + + it('should return 400 if try to resume not paused milestone', (done) => { + const newBody = _.cloneDeep(body); + newBody.status = 'resume'; + request(server) + .patch('/v5/timelines/1/milestones/2') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newBody) + .expect(400, done); + }); + + it('should return 200 if try to resume then status should update to last status and ' + + 'should have one status history created', (done) => { + const newBody = _.cloneDeep(body); + newBody.status = 'resume'; + newBody.statusComment = 'new comment'; + models.Milestone.bulkCreate([ + { + id: 7, + timelineId: 1, + name: 'Milestone 1 [paused]', + duration: 2, + startDate: '2018-05-13T00:00:00.000Z', + endDate: '2018-05-14T00:00:00.000Z', + completionDate: '2018-05-16T00:00:00.000Z', + status: 'active', + type: 'type1', + details: { + detail1: { + subDetail1A: 1, + subDetail1B: 2, + }, + detail2: [1, 2, 3], + }, + order: 1, + plannedText: 'plannedText 1', + activeText: 'activeText 1', + completedText: 'completedText 1', + blockedText: 'blockedText 1', + createdBy: 1, + updatedBy: 2, + createdAt: '2018-05-11T00:00:00.000Z', + updatedAt: '2018-05-11T00:00:00.000Z', + }, + ]).then(() => models.Milestone.findByPk(7) + // pause milestone before resume + .then(milestone => milestone.update(_.assign({}, milestone.toJSON(), { status: 'paused' }))), + ).then(() => { + request(server) + .patch('/v5/timelines/1/milestones/7') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newBody) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + models.Milestone.findByPk(7).then((milestone) => { + milestone.status.should.be.eql('active'); + + return models.StatusHistory.findAll({ + where: { + reference: 'milestone', + referenceId: milestone.id, + status: 'active', + comment: 'new comment', + }, + paranoid: false, + }).then((statusHistories) => { + statusHistories.length.should.be.eql(1); + done(); + }).catch(done); + }).catch(done); + } + }); + }); + }); + describe('Bus api', () => { let createEventSpy; const sandbox = sinon.sandbox.create(); diff --git a/src/routes/permissions/get.js b/src/routes/permissions/get.js new file mode 100644 index 00000000..d22acbee --- /dev/null +++ b/src/routes/permissions/get.js @@ -0,0 +1,65 @@ +/** + * API to get project permissions + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('permissions.view'), + (req, res, next) => { + const projectId = req.params.projectId; + return models.Project.findOne({ + where: { + id: projectId, + }, + }) + .then((project) => { + if (!project) { + const apiErr = new Error(`Project not found for id ${projectId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + if (!project.templateId) { + return Promise.resolve([]); + } + + return models.WorkManagementPermission.findAll({ + where: { + projectTemplateId: project.templateId, + }, + }); + }) + .then((workManagementPermissions) => { + const allowPermissions = {}; + + // find all allowed permissions + workManagementPermissions.forEach((workManagementPermission) => { + const isAllowed = util.hasPermission( + workManagementPermission.permission, + req.authUser, + req.context.currentProjectMembers, + ); + + if (isAllowed) { + allowPermissions[workManagementPermission.policy] = true; + } + }); + + res.json(allowPermissions); + }) + .catch(next); + }, +]; diff --git a/src/routes/permissions/get.spec.js b/src/routes/permissions/get.spec.js new file mode 100644 index 00000000..57f7d62e --- /dev/null +++ b/src/routes/permissions/get.spec.js @@ -0,0 +1,233 @@ +/* eslint-disable no-unused-expressions */ +/** + * Tests for get.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +chai.should(); + +describe('GET permissions', () => { + let projectId; + let templateId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + const permissions = [ + { + policy: 'work.create', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }, + { + policy: 'work.edit', + permission: { + allowRule: { + projectRoles: ['copilot'], + topcoderRoles: ['Connect Manager'], + }, + denyRule: { topcoderRoles: ['Connect Admin'] }, + }, + createdBy: 1, + updatedBy: 1, + }, + ]; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((t) => { + templateId = t.id; + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]).then(() => { + const newPermissions = _.map(permissions, p => _.assign({}, p, { projectTemplateId: templateId })); + models.WorkManagementPermission.bulkCreate(newPermissions, { returning: true }) + .then(() => done()); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/permissions', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get(`/v5/projects/${projectId}/permissions`) + .expect(403, done); + }); + + it('should return 403 for non-member', (done) => { + request(server) + .get(`/v5/projects/${projectId}/permissions`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .expect(403, done); + }); + + it('should return 404 for non-existed project', (done) => { + request(server) + .get('/v5/projects/9999/permissions') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 200 for project with no template', (done) => { + models.Project.create({ + type: 'generic', + name: 'test1', + status: 'draft', + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((p) => { + request(server) + .get(`/v5/projects/${p.id}/permissions`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.be.empty; + done(); + }); + }); + }); + + it('should return 200 for connect admin - no permission', (done) => { + request(server) + .get(`/v5/projects/${projectId}/permissions`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.not.have.all.keys(permissions[0].policy, permissions[1].policy); + done(); + }); + }); + + it('should return 200 for copilot - has both no-permission and permission', (done) => { + request(server) + .get(`/v5/projects/${projectId}/permissions`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.have.all.keys(permissions[1].policy); + resJson.should.not.have.all.keys(permissions[0].policy); + done(); + }); + }); + + it('should return 200 for admin - has both permission and no-permission', (done) => { + request(server) + .get(`/v5/projects/${projectId}/permissions`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.have.all.keys(permissions[0].policy); + resJson.should.not.have.all.keys(permissions[1].policy); + done(); + }); + }); + + it('should return 200 for manager - has permissions', (done) => { + request(server) + .get(`/v5/projects/${projectId}/permissions`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.have.all.keys(permissions[0].policy, permissions[1].policy); + done(); + }); + }); + }); +}); diff --git a/src/routes/phaseProducts/create.spec.js b/src/routes/phaseProducts/create.spec.js index 1e51d383..e967997f 100644 --- a/src/routes/phaseProducts/create.spec.js +++ b/src/routes/phaseProducts/create.spec.js @@ -177,7 +177,7 @@ describe('Phase Products', () => { request(server) .post(`/v5/projects/99999/phases/${phaseId}/products`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect('Content-Type', /json/) @@ -188,7 +188,7 @@ describe('Phase Products', () => { request(server) .post(`/v5/projects/${projectId}/phases/99999/products`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send(body) .expect('Content-Type', /json/) diff --git a/src/routes/phaseProducts/delete.spec.js b/src/routes/phaseProducts/delete.spec.js index dbbd677f..76b609fa 100644 --- a/src/routes/phaseProducts/delete.spec.js +++ b/src/routes/phaseProducts/delete.spec.js @@ -152,7 +152,7 @@ describe('Phase Products', () => { request(server) .delete(`/v5/projects/999/phases/${phaseId}/products/${productId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect('Content-Type', /json/) .expect(404, done); @@ -162,7 +162,7 @@ describe('Phase Products', () => { request(server) .delete(`/v5/projects/${projectId}/phases/99999/products/${productId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect('Content-Type', /json/) .expect(404, done); @@ -172,7 +172,7 @@ describe('Phase Products', () => { request(server) .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .expect('Content-Type', /json/) .expect(404, done); diff --git a/src/routes/phaseProducts/list.spec.js b/src/routes/phaseProducts/list.spec.js index 7a5e39da..3a0fc37d 100644 --- a/src/routes/phaseProducts/list.spec.js +++ b/src/routes/phaseProducts/list.spec.js @@ -1,7 +1,7 @@ /* eslint-disable no-unused-expressions */ import _ from 'lodash'; import request from 'supertest'; -import sleep from 'sleep'; +// import sleep from 'sleep'; import chai from 'chai'; import config from 'config'; import server from '../../app'; @@ -111,7 +111,7 @@ describe('Phase Products', () => { body: project, }).then(() => { // sleep for some time, let elasticsearch indices be settled - sleep.sleep(5); + // sleep.sleep(5); done(); }); }); diff --git a/src/routes/phaseProducts/update.spec.js b/src/routes/phaseProducts/update.spec.js index df8c5676..3fc58dbe 100644 --- a/src/routes/phaseProducts/update.spec.js +++ b/src/routes/phaseProducts/update.spec.js @@ -144,7 +144,7 @@ describe('Phase Products', () => { request(server) .patch(`/v5/projects/999/phases/${phaseId}/products/${productId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(updateBody) .expect('Content-Type', /json/) @@ -155,7 +155,7 @@ describe('Phase Products', () => { request(server) .patch(`/v5/projects/${projectId}/phases/99999/products/${productId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send(updateBody) .expect('Content-Type', /json/) @@ -166,7 +166,7 @@ describe('Phase Products', () => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send(updateBody) .expect('Content-Type', /json/) @@ -177,7 +177,7 @@ describe('Phase Products', () => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ estimatedPrice: -15, diff --git a/src/routes/phases/create.js b/src/routes/phases/create.js index 59aaee7b..3d1666a6 100644 --- a/src/routes/phases/create.js +++ b/src/routes/phases/create.js @@ -5,7 +5,7 @@ import Sequelize from 'sequelize'; import models from '../../models'; import util from '../../util'; -import { EVENT, RESOURCES } from '../../constants'; +import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants'; const permissions = require('tc-core-library-js').middleware.permissions; @@ -13,6 +13,8 @@ const permissions = require('tc-core-library-js').middleware.permissions; const addProjectPhaseValidations = { body: Joi.object().keys({ name: Joi.string().required(), + description: Joi.string().optional(), + requirements: Joi.string().optional(), status: Joi.string().required(), startDate: Joi.date().optional(), endDate: Joi.date().optional(), @@ -139,7 +141,7 @@ module.exports = [ // Send events to buses req.log.debug('Sending event to RabbitMQ bus for project phase %d', newProjectPhase.id); req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, - newProjectPhase, + { added: newProjectPhase, route: TIMELINE_REFERENCES.PHASE }, { correlationId: req.id }, ); diff --git a/src/routes/phases/create.spec.js b/src/routes/phases/create.spec.js index 693683c9..fecb003f 100644 --- a/src/routes/phases/create.spec.js +++ b/src/routes/phases/create.spec.js @@ -222,7 +222,7 @@ describe('Project Phases', () => { request(server) .post('/v5/projects/99999/phases/') .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(body) .expect('Content-Type', /json/) diff --git a/src/routes/phases/delete.js b/src/routes/phases/delete.js index d035cc94..a7e6d2a0 100644 --- a/src/routes/phases/delete.js +++ b/src/routes/phases/delete.js @@ -4,7 +4,7 @@ import _ from 'lodash'; 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, TIMELINE_REFERENCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -41,7 +41,7 @@ module.exports = [ // Send events to buses req.app.services.pubsub.publish( EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, - deleted, + { deleted, route: TIMELINE_REFERENCES.PHASE }, { correlationId: req.id }, ); diff --git a/src/routes/phases/delete.spec.js b/src/routes/phases/delete.spec.js index 7bf7e1e8..2e8d04ee 100644 --- a/src/routes/phases/delete.spec.js +++ b/src/routes/phases/delete.spec.js @@ -140,7 +140,7 @@ describe('Project Phases', () => { request(server) .delete(`/v5/projects/999/phases/${phaseId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect('Content-Type', /json/) .expect(404, done); @@ -150,7 +150,7 @@ describe('Project Phases', () => { request(server) .delete(`/v5/projects/${projectId}/phases/999`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .expect('Content-Type', /json/) .expect(404, done); diff --git a/src/routes/phases/list.js b/src/routes/phases/list.js index fd063a95..c7697239 100644 --- a/src/routes/phases/list.js +++ b/src/routes/phases/list.js @@ -64,7 +64,7 @@ module.exports = [ if (!project) { const apiErr = new Error(`active project not found for project id ${projectId}`); apiErr.status = 404; - next(apiErr); + return next(apiErr); } // Get the phases diff --git a/src/routes/phases/list.spec.js b/src/routes/phases/list.spec.js index 1fc977c0..d4d93a7a 100644 --- a/src/routes/phases/list.spec.js +++ b/src/routes/phases/list.spec.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import request from 'supertest'; import config from 'config'; -import sleep from 'sleep'; +// import sleep from 'sleep'; import chai from 'chai'; import server from '../../app'; import models from '../../models'; @@ -95,7 +95,7 @@ describe('Project Phases', () => { body: project, }).then(() => { // sleep for some time, let elasticsearch indices be settled - sleep.sleep(5); + // sleep.sleep(5); done(); }); }); diff --git a/src/routes/phases/update.js b/src/routes/phases/update.js index bf85ffb3..7c82ac15 100644 --- a/src/routes/phases/update.js +++ b/src/routes/phases/update.js @@ -6,7 +6,7 @@ import Sequelize from 'sequelize'; 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, TIMELINE_REFERENCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -14,6 +14,8 @@ const permissions = tcMiddleware.permissions; const updateProjectPhaseValidation = { body: Joi.object().keys({ name: Joi.string().optional(), + description: Joi.string().optional(), + requirements: Joi.string().optional(), status: Joi.string().optional(), startDate: Joi.date().optional(), endDate: Joi.date().optional(), @@ -152,7 +154,7 @@ module.exports = [ // emit original and updated project phase information req.app.services.pubsub.publish( EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, - { original: previousValue, updated, allPhases }, + { original: previousValue, updated, allPhases, route: TIMELINE_REFERENCES.PHASE }, { correlationId: req.id }, ); diff --git a/src/routes/phases/update.spec.js b/src/routes/phases/update.spec.js index bb68211b..19ea1262 100644 --- a/src/routes/phases/update.spec.js +++ b/src/routes/phases/update.spec.js @@ -151,7 +151,7 @@ describe('Project Phases', () => { request(server) .patch(`/v5/projects/999/phases/${phaseId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(updateBody) .expect('Content-Type', /json/) @@ -162,7 +162,7 @@ describe('Project Phases', () => { request(server) .patch(`/v5/projects/${projectId}/phases/999`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send(updateBody) .expect('Content-Type', /json/) @@ -186,7 +186,7 @@ describe('Project Phases', () => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ endDate: '2018-05-13T00:00:00Z', diff --git a/src/routes/planConfig/version/create.spec.js b/src/routes/planConfig/version/create.spec.js index 1b564d7b..d3c19562 100644 --- a/src/routes/planConfig/version/create.spec.js +++ b/src/routes/planConfig/version/create.spec.js @@ -55,7 +55,8 @@ describe('CREATE PlanConfig version', () => { request(server) .post('/v5/projects/metadata/planConfig/dev/versions') .send(body) - .expect(403, done); + .expect(403) + .end(done); }); it('should return 400 if missing config', (done) => { @@ -70,7 +71,8 @@ describe('CREATE PlanConfig version', () => { }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(400, done); + .expect(400) + .end(done); }); it('should return 201 for admin', (done) => { @@ -106,7 +108,8 @@ describe('CREATE PlanConfig version', () => { Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(body) - .expect(403, done); + .expect(403) + .end(done); }); }); }); diff --git a/src/routes/planConfig/version/delete.spec.js b/src/routes/planConfig/version/delete.spec.js index a47b5091..79097e62 100644 --- a/src/routes/planConfig/version/delete.spec.js +++ b/src/routes/planConfig/version/delete.spec.js @@ -70,7 +70,8 @@ describe('DELETE planConfig version', () => { it('should return 403 if user is not authenticated', (done) => { request(server) .delete('/v5/projects/metadata/planConfig/dev/versions/1') - .expect(403, done); + .expect(403) + .end(done); }); it('should return 403 for member', (done) => { @@ -79,7 +80,8 @@ describe('DELETE planConfig version', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .expect(403, done); + .expect(403) + .end(done); }); it('should return 403 for copilot', (done) => { @@ -88,7 +90,8 @@ describe('DELETE planConfig version', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .expect(403, done); + .expect(403) + .end(done); }); it('should return 403 for manager', (done) => { @@ -97,7 +100,8 @@ describe('DELETE planConfig version', () => { .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) - .expect(403, done); + .expect(403) + .end(done); }); it('should return 404 for non-existed key', (done) => { @@ -106,7 +110,8 @@ describe('DELETE planConfig version', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(404, done); + .expect(404) + .end(done); }); it('should return 404 for non-existed version', (done) => { @@ -115,7 +120,8 @@ describe('DELETE planConfig version', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(404, done); + .expect(404) + .end(done); }); it('should return 204, for admin', (done) => { diff --git a/src/routes/planConfig/version/get.spec.js b/src/routes/planConfig/version/get.spec.js index d608a761..a7455791 100644 --- a/src/routes/planConfig/version/get.spec.js +++ b/src/routes/planConfig/version/get.spec.js @@ -92,7 +92,8 @@ describe('GET a latest version of specific key of PlanConfig', () => { it('should return 403 if user is not authenticated', (done) => { request(server) .get('/v5/projects/metadata/planConfig/dev') - .expect(403, done); + .expect(403) + .end(done); }); it('should return 200 for connect admin', (done) => { @@ -121,7 +122,8 @@ describe('GET a latest version of specific key of PlanConfig', () => { .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .expect(200, done); + .expect(200) + .end(done); }); it('should return 200 for copilot', (done) => { @@ -130,7 +132,8 @@ describe('GET a latest version of specific key of PlanConfig', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .expect(200, done); + .expect(200) + .end(done); }); }); }); diff --git a/src/routes/planConfig/version/getVersion.js b/src/routes/planConfig/version/getVersion.js index ae2d722e..ca0179f1 100644 --- a/src/routes/planConfig/version/getVersion.js +++ b/src/routes/planConfig/version/getVersion.js @@ -44,15 +44,7 @@ module.exports = [ .then((data) => { if (data.length === 0) { req.log.debug('No planConfig found in ES'); - models.PlanConfig.findOne({ - where: { - key: req.params.key, - version: req.params.version, - }, - order: [['revision', 'DESC']], - limit: 1, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) + models.PlanConfig.findOneWithLatestRevision(req.params) .then((planConfig) => { // Not found if (!planConfig) { diff --git a/src/routes/planConfig/version/update.spec.js b/src/routes/planConfig/version/update.spec.js index b59283c1..025b2b08 100644 --- a/src/routes/planConfig/version/update.spec.js +++ b/src/routes/planConfig/version/update.spec.js @@ -55,7 +55,8 @@ describe('UPDATE PlanConfig version', () => { request(server) .patch('/v5/projects/metadata/planConfig/dev/versions/1') .send(body) - .expect(403, done); + .expect(403) + .end(done); }); it('should return 400 if missing config', (done) => { @@ -69,7 +70,8 @@ describe('UPDATE PlanConfig version', () => { }) .send(invalidBody) .expect('Content-Type', /json/) - .expect(400, done); + .expect(400) + .end(done); }); it('should return 201 for admin', (done) => { @@ -105,7 +107,8 @@ describe('UPDATE PlanConfig version', () => { Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(body) - .expect(403, done); + .expect(403) + .end(done); }); }); }); diff --git a/src/routes/priceConfig/version/getVersion.js b/src/routes/priceConfig/version/getVersion.js index c0593fd8..2121ab59 100644 --- a/src/routes/priceConfig/version/getVersion.js +++ b/src/routes/priceConfig/version/getVersion.js @@ -44,15 +44,7 @@ module.exports = [ .then((data) => { if (data.length === 0) { req.log.debug('No priceConfig found in ES'); - models.PriceConfig.findOne({ - where: { - key: req.params.key, - version: req.params.version, - }, - order: [['revision', 'DESC']], - limit: 1, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) + models.PriceConfig.findOneWithLatestRevision(req.params) .then((priceConfig) => { // Not found if (!priceConfig) { diff --git a/src/routes/projectEstimationItems/list.js b/src/routes/projectEstimationItems/list.js new file mode 100644 index 00000000..c958c756 --- /dev/null +++ b/src/routes/projectEstimationItems/list.js @@ -0,0 +1,49 @@ +/** + * API to get project estimation items + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + estimationId: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('projectEstimation.item.list'), + (req, res, next) => models.ProjectEstimation.findOne({ + where: { + id: req.params.estimationId, + projectId: req.params.projectId, + deletedAt: { $eq: null }, + }, + raw: true, + }).then((estimation) => { + if (!estimation) { + const apiErr = new Error('Project Estimation not found for projectId ' + + `${req.params.projectId} and estimation id ${req.params.estimationId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + return models.ProjectEstimationItem.findAll({ + where: { + projectEstimationId: req.params.estimationId, + deletedAt: { $eq: null }, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + reqUser: req.authUser, + members: req.context.currentProjectMembers, + }).then((items) => { + res.json(items); + return Promise.resolve(); + }); + }).catch(next), +]; diff --git a/src/routes/projectEstimationItems/list.spec.js b/src/routes/projectEstimationItems/list.spec.js new file mode 100644 index 00000000..6e3c4443 --- /dev/null +++ b/src/routes/projectEstimationItems/list.spec.js @@ -0,0 +1,233 @@ +/** + * Tests for list.js + */ +import chai from 'chai'; +import request from 'supertest'; +import _ from 'lodash'; +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +const project = { + id: 1, + name: 'test project 1', + type: 'generic', + status: 'active', + createdBy: 1, + updatedBy: 1, + lastActivityAt: new Date(), + lastActivityUserId: 1, +}; + +const projectMembers = [ + { + userId: testUtil.userIds.admin, + role: 'manager', + projectId: 1, + createdBy: 1, + updatedBy: 1, + }, + { + userId: testUtil.userIds.copilot, + role: 'copilot', + projectId: 1, + createdBy: 1, + updatedBy: 1, + }, + { + userId: testUtil.userIds.member, + role: 'manager', + projectId: 1, + createdBy: 1, + updatedBy: 1, + }, +]; + +const projectEstimations = [ + { + id: 1, + buildingBlockKey: 'key1', + conditions: ' empty condition ', + price: 1000, + quantity: 100, + minTime: 2, + maxTime: 2, + metadata: {}, + projectId: 1, + createdBy: 1, + updatedBy: 1, + }, +]; + +const projectEstimationItems = [ + { + projectEstimationId: 1, + price: 1234, + type: 'community', + markupUsedReference: 'buildingBlock', + markupUsedReferenceId: 1, + createdBy: 1, + updatedBy: 1, + }, + { + projectEstimationId: 1, + price: 5678, + type: 'topcoder_service', + markupUsedReference: 'buildingBlock', + markupUsedReferenceId: 1, + createdBy: 1, + updatedBy: 1, + }, + { + projectEstimationId: 1, + price: 1982, + type: 'fee', + markupUsedReference: 'buildingBlock', + markupUsedReferenceId: 1, + createdBy: 1, + updatedBy: 1, + }, +]; + +describe('GET project estimation items', () => { + beforeEach(() => testUtil.clearDb() + .then(() => models.Project.create(project)) + .then(() => models.ProjectMember.bulkCreate(projectMembers)) + .then(() => models.ProjectEstimation.bulkCreate(projectEstimations)) + .then(() => models.ProjectEstimationItem.bulkCreate(projectEstimationItems)) + .then(() => Promise.resolve()), + ); + after((done) => { + testUtil.clearDb(done); + }); + + const url = '/v5/projects/1/estimations/1/items'; + + describe(`GET ${url}`, () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get(url) + .expect(403) + .end(done); + }); + + it('should return 403 if user is not copilot or above', (done) => { + request(server) + .get(url) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .expect(403) + .end(done); + }); + + it('should return 404 if project not exists', (done) => { + request(server) + .get('/v5/projects/999/estimations/1/items') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404) + .end(done); + }); + + it('should return 404 if project estimation not exists', (done) => { + request(server) + .get('/v5/projects/1/estimations/999/items') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404) + .end(done); + }); + + it('should return all project estimation items for admin', (done) => { + request(server) + .get(url) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.length.should.be.eql(3); + // convert items to map with type. + const itemMap = {}; + _.forEach(resJson, (item) => { + itemMap[item.type] = item; + }); + should.exist(itemMap.community); + itemMap.community.price.should.be.eql(1234); + itemMap.community.projectEstimationId.should.be.eql(1); + itemMap.community.type.should.be.eql('community'); + + should.exist(itemMap.topcoder_service); + itemMap.topcoder_service.price.should.be.eql(5678); + itemMap.topcoder_service.projectEstimationId.should.be.eql(1); + itemMap.topcoder_service.type.should.be.eql('topcoder_service'); + + should.exist(itemMap.fee); + itemMap.fee.price.should.be.eql(1982); + itemMap.fee.projectEstimationId.should.be.eql(1); + itemMap.fee.type.should.be.eql('fee'); + + done(); + } + }); + }); + + it('should return 1 project estimation item for copilot', (done) => { + request(server) + .get(url) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.length.should.be.eql(1); + // convert items to map with type. + should.exist(resJson[0]); + + const item = resJson[0]; + + item.price.should.be.eql(1234); + item.projectEstimationId.should.be.eql(1); + item.type.should.be.eql('community'); + + done(); + } + }); + }); + + it('should return 0 project estimation items for a project manager who is not a topcoder manager', (done) => { + request(server) + .get(url) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.length.should.be.eql(0); + // convert items to map with type. + done(); + } + }); + }); + }); +}); diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index b9e68fce..8f1a9827 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -7,8 +7,9 @@ import config from 'config'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, - MANAGER_ROLES, INVITE_STATUS, EVENT, RESOURCES, USER_ROLE } from '../../constants'; +import { PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, MANAGER_ROLES, INVITE_STATUS, + EVENT, RESOURCES, BUS_API_EVENT, USER_ROLE, MAX_PARALLEL_REQUEST_QTY } from '../../constants'; +import { createEvent } from '../../services/busApi'; /** * API to create member invite to project. @@ -56,14 +57,25 @@ const compareEmail = (email1, email2, options = { UNIQUE_GMAIL_VALIDATION: false * @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 * * @returns {Promise} list of promises */ -const buildCreateInvitePromises = (req, invite, invites, data, failed) => { +const buildCreateInvitePromises = (req, invite, invites, data, failed, members) => { const invitePromises = []; if (invite.userIds) { // remove invites for users that are invited already - _.remove(invite.userIds, u => _.some(invites, i => i.userId === u)); + const errMessageForAlreadyInvitedUsers = 'User with such handle is already invited to this project.'; + _.remove(invite.userIds, u => _.some(invites, (i) => { + const isPresent = i.userId === u; + if (isPresent) { + failed.push(_.assign({}, { + userId: u, + message: errMessageForAlreadyInvitedUsers, + })); + } + return isPresent; + })); invite.userIds.forEach((userId) => { const dataNew = _.clone(data); @@ -76,7 +88,7 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => { if (invite.emails) { // if for some emails there are already existent users, we will invite them by userId, // to avoid sending them registration email - return util.lookupUserEmails(req, invite.emails) + return util.lookupMultipleUserEmails(req, invite.emails, MAX_PARALLEL_REQUEST_QTY) .then((existentUsers) => { // existent user we will invite by userId and email const existentUsersWithNumberId = existentUsers.map((user) => { @@ -91,23 +103,64 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => { !_.find(existentUsers, existentUser => compareEmail(existentUser.email, inviteEmail, { UNIQUE_GMAIL_VALIDATION: false })), ); + + // remove users that are already member of the team + const errMessageForAlreadyMemberUsers = 'User with such email is already a member of the team.'; + + _.remove(existentUsersWithNumberId, user => _.some(members, (m) => { + const isPresent = (m.userId === user.id); + if (isPresent) { + failed.push(_.assign({}, { + email: user.email, + message: errMessageForAlreadyMemberUsers, + })); + } + return isPresent; + })); + // remove invites for users that are invited already - _.remove(existentUsersWithNumberId, user => _.some(invites, i => i.userId === user.id)); + const errMessageForAlreadyInvitedUsers = 'User with such email is already invited to this project.'; + + _.remove(existentUsersWithNumberId, user => _.some(invites, (i) => { + const isPresent = (i.userId === user.id); + if (isPresent) { + failed.push(_.assign({}, { + email: i.email, + message: errMessageForAlreadyInvitedUsers, + })); + } + return isPresent; + })); + existentUsersWithNumberId.forEach((user) => { const dataNew = _.clone(data); dataNew.userId = user.id; - dataNew.email = user.email; + dataNew.email = user.email ? user.email.toLowerCase() : user.email; + invitePromises.push(models.ProjectMemberInvite.create(dataNew)); }); + // remove invites for users that are invited already _.remove(nonExistentUserEmails, email => - _.some(invites, i => - compareEmail(i.email, email, { UNIQUE_GMAIL_VALIDATION: config.get('UNIQUE_GMAIL_VALIDATION') }))); + _.some(invites, (i) => { + const areEmailsSame = compareEmail(i.email, email, { + UNIQUE_GMAIL_VALIDATION: config.get('UNIQUE_GMAIL_VALIDATION'), + }); + if (areEmailsSame) { + failed.push(_.assign({}, { + email: i.email, + message: errMessageForAlreadyInvitedUsers, + })); + } + return areEmailsSame; + }), + ); nonExistentUserEmails.forEach((email) => { const dataNew = _.clone(data); - dataNew.email = email; + dataNew.email = email.toLowerCase(); + invitePromises.push(models.ProjectMemberInvite.create(dataNew)); }); return invitePromises; @@ -120,8 +173,9 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed) => { return invitePromises; }; -const sendInviteEmail = (req, projectId) => { +const sendInviteEmail = (req, projectId, invite) => { req.log.debug(req.authUser); + const emailEventType = BUS_API_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; const promises = [ models.Project.findOne({ where: { id: projectId }, @@ -131,6 +185,40 @@ const sendInviteEmail = (req, projectId) => { ]; return Promise.all(promises).then((responses) => { req.log.debug(responses); + const project = responses[0]; + const initiator = responses[1] && responses[1].length ? responses[1][0] : { + userId: req.authUser.userId, + firstName: 'Connect', + lastName: 'User', + }; + createEvent(emailEventType, { + data: { + connectURL: config.get('connectUrl'), + accountsAppURL: config.get('accountsAppUrl'), + subject: config.get('inviteEmailSubject'), + projects: [{ + name: project.name, + projectId, + sections: [ + { + EMAIL_INVITES: true, + title: config.get('inviteEmailSectionTitle'), + projectName: project.name, + projectId, + initiator, + isSSO: util.isSSO(project), + }, + ], + }], + }, + recipients: [invite.email], + version: 'v3', + from: { + name: config.get('EMAIL_INVITE_FROM_NAME'), + email: config.get('EMAIL_INVITE_FROM_EMAIL'), + }, + categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], + }, req.log); }).catch((error) => { req.log.error(error); }); @@ -160,9 +248,19 @@ module.exports = [ 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 => m.userId === u)); + _.remove(invite.userIds, u => _.some(members, (m) => { + const isPresent = m.userId === u; + if (isPresent) { + failed.push(_.assign({}, { + userId: m.userId, + message: errorMessageForAlreadyMemberUser, + })); + } + return isPresent; + })); // permission: // user has to have constants.MANAGER_ROLES role // to be invited as PROJECT_MEMBER_ROLE.MANAGER @@ -187,7 +285,7 @@ module.exports = [ } return Promise.all(promises).then((rolesList) => { if (!!invite.userIds && _.includes(PROJECT_MEMBER_MANAGER_ROLES, invite.role)) { - req.log.debug('Chekcing if userId is allowed as manager'); + req.log.debug('Checking if userId is allowed as manager'); const forbidUserList = []; _.zip(invite.userIds, rolesList).forEach((data) => { const [userId, roles] = data; @@ -218,7 +316,8 @@ module.exports = [ }; req.log.debug('Creating invites'); - return models.Sequelize.Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed)) + const invitePromises = buildCreateInvitePromises(req, invite, invites, data, failed, members); + return models.Sequelize.Promise.all(invitePromises) .then((values) => { values.forEach((v) => { // emit the event @@ -235,7 +334,7 @@ module.exports = [ ); // send email invite (async) if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) { - sendInviteEmail(req, projectId); + sendInviteEmail(req, projectId, v); } }); return values; diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index e57c04c6..fa2ad92a 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -9,7 +9,13 @@ import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { USER_ROLE, PROJECT_MEMBER_ROLE, INVITE_STATUS, BUS_API_EVENT, RESOURCES } from '../../constants'; +import { + USER_ROLE, + PROJECT_MEMBER_ROLE, + INVITE_STATUS, + BUS_API_EVENT, + RESOURCES, +} from '../../constants'; const should = chai.should(); @@ -51,6 +57,15 @@ describe('Project Member Invite create', () => { createdBy: 1, updatedBy: 1, }); + + models.ProjectMember.create({ + userId: 40158431, + projectId: project1.id, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); }).then(() => models.Project.create({ type: 'generic', @@ -147,9 +162,9 @@ describe('Project Member Invite create', () => { server.services.pubsub.publish.restore(); sinon.stub(server.services.pubsub, 'init', () => {}); sinon.stub(server.services.pubsub, 'publish', () => {}); - // by default mock lookupUserEmails return nothing so all the cases are not broken + // by default mock lookupMultipleUserEmails return nothing so all the cases are not broken sandbox.stub(util, 'getUserRoles', () => Promise.resolve([])); - sandbox.stub(util, 'lookupUserEmails', () => Promise.resolve([])); + sandbox.stub(util, 'lookupMultipleUserEmails', () => Promise.resolve([])); sandbox.stub(util, 'getMemberDetailsByUserIds', () => Promise.resolve([{ userId: 40051333, firstName: 'Admin', @@ -168,7 +183,7 @@ describe('Project Member Invite create', () => { Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - userIds: [40051332], + userIds: [40051331], emails: ['hello@world.com'], role: 'customer', }) @@ -178,7 +193,7 @@ describe('Project Member Invite create', () => { if (err) { done(err); } else { - const resJson = res.body.success[0]; + const resJson = res.body.success[1]; should.exist(resJson); resJson.role.should.equal('customer'); resJson.projectId.should.equal(project1.id); @@ -358,8 +373,8 @@ describe('Project Member Invite create', () => { }), }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); - util.lookupUserEmails.restore(); - sandbox.stub(util, 'lookupUserEmails', () => Promise.resolve([{ + util.lookupMultipleUserEmails.restore(); + sandbox.stub(util, 'lookupMultipleUserEmails', () => Promise.resolve([{ id: '12345', email: 'hello@world.com', }])); @@ -436,7 +451,104 @@ describe('Project Member Invite create', () => { }); }); - it('should return 201 and empty response when trying add already invited member', (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: { + 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`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + userIds: [40158431], + role: 'customer', + }) + .expect('Content-Type', /json/) + .expect(403) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.failed; + should.exist(resJson); + resJson[0].userId.should.equal(40158431); + 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; + done(); + } + }); + }); + + it('should return 403 and failed list when trying add already team member by email', (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); + util.lookupMultipleUserEmails.restore(); + sandbox.stub(util, 'lookupMultipleUserEmails', () => Promise.resolve([{ + id: '40158431', + email: 'romit.choudhary@rivigo.com', + }])); + request(server) + .post(`/v5/projects/${project1.id}/members/invite`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + emails: ['romit.choudhary@rivigo.com'], + role: 'customer', + }) + .expect('Content-Type', /json/) + .expect(403) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.failed; + should.exist(resJson); + resJson[0].email.should.equal('romit.choudhary@rivigo.com'); + resJson[0].message.should.equal('User with such email is already a member of the team.'); + resJson.length.should.equal(1); + server.services.pubsub.publish.neverCalledWith('project.member.invite.created').should.be.true; + done(); + } + }); + }); + + 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, @@ -466,14 +578,16 @@ describe('Project Member Invite create', () => { role: 'customer', }) .expect('Content-Type', /json/) - .expect(201) + .expect(403) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.success; + const resJson = res.body.failed; should.exist(resJson); - resJson.length.should.equal(0); + resJson.length.should.equal(1); + resJson[0].userId.should.equal(40051335); + 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(); } @@ -657,7 +771,7 @@ describe('Project Member Invite create', () => { }); }); - it('should return 201 and empty response when trying add already invited member by lowercase email', (done) => { + 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`) .set({ @@ -668,20 +782,20 @@ describe('Project Member Invite create', () => { role: 'customer', }) .expect('Content-Type', /json/) - .expect(201) + .expect(403) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.success; + const resJson = res.body.failed; should.exist(resJson); - resJson.length.should.equal(0); + resJson.length.should.equal(1); done(); } }); }); - it('should return 201 and empty response when trying add already invited member by uppercase email', (done) => { + 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`) .set({ @@ -692,20 +806,20 @@ describe('Project Member Invite create', () => { role: 'customer', }) .expect('Content-Type', /json/) - .expect(201) + .expect(403) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.success; + const resJson = res.body.failed; should.exist(resJson); - resJson.length.should.equal(0); + resJson.length.should.equal(1); done(); } }); }); - xit('should return 201 and empty response when trying add already invited member by gmail email with dot', + 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`) @@ -717,20 +831,20 @@ describe('Project Member Invite create', () => { role: 'customer', }) .expect('Content-Type', /json/) - .expect(201) + .expect(403) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.success; + const resJson = res.body.failed; should.exist(resJson); - resJson.length.should.equal(0); + resJson.length.should.equal(1); done(); } }); }); - xit('should return 201 and empty response when trying add already invited member by gmail email without dot', + 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`) @@ -742,14 +856,14 @@ describe('Project Member Invite create', () => { role: 'customer', }) .expect('Content-Type', /json/) - .expect(201) + .expect(403) .end((err, res) => { if (err) { done(err); } else { - const resJson = res.body.success; + const resJson = res.body.failed; should.exist(resJson); - resJson.length.should.equal(0); + resJson.length.should.equal(1); done(); } }); @@ -849,7 +963,7 @@ describe('Project Member Invite create', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; + createEventSpy.calledTwice.should.be.true; createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED).should.be.true; createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE })).should.be.true; diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index 31ba995a..a6cc9acd 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -16,7 +16,15 @@ const permissions = tcMiddleware.permissions; const createProjectMemberValidations = { body: Joi.object().keys({ role: Joi.any() - .valid(PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.COPILOT), + .valid( + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, + PROJECT_MEMBER_ROLE.COPILOT, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE, + ), }), }; @@ -36,9 +44,49 @@ module.exports = [ return next(err); } + if (PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT === targetRole && + !util.hasRoles(req, [USER_ROLE.SOLUTION_ARCHITECT])) { + const err = new Error(`Only solution architect is able to join as ${targetRole}`); + err.status = 401; + return next(err); + } + + if (PROJECT_MEMBER_ROLE.PROJECT_MANAGER === targetRole && + !util.hasRoles(req, [USER_ROLE.PROJECT_MANAGER])) { + const err = new Error(`Only project manager is able to join as ${targetRole}`); + err.status = 401; + return next(err); + } + + if (PROJECT_MEMBER_ROLE.PROGRAM_MANAGER === targetRole && + !util.hasRoles(req, [USER_ROLE.PROGRAM_MANAGER])) { + const err = new Error(`Only program manager is able to join as ${targetRole}`); + err.status = 401; + return next(err); + } + + if (PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE === targetRole && + !util.hasRoles(req, [USER_ROLE.ACCOUNT_EXECUTIVE])) { + const err = new Error(`Only account executive is able to join as ${targetRole}`); + err.status = 401; + return next(err); + } + if (PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER === targetRole && - !util.hasRoles(req, [USER_ROLE.MANAGER, USER_ROLE.TOPCODER_ACCOUNT_MANAGER])) { - const err = new Error(`Only manager or account manager is able to join as ${targetRole}`); + !util.hasRoles(req, [ + USER_ROLE.MANAGER, + USER_ROLE.TOPCODER_ACCOUNT_MANAGER, + USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE, + USER_ROLE.PRESALES, + USER_ROLE.ACCOUNT_EXECUTIVE, + USER_ROLE.PROGRAM_MANAGER, + USER_ROLE.SOLUTION_ARCHITECT, + USER_ROLE.PROJECT_MANAGER, + ])) { + const err = new Error( + // eslint-disable-next-line max-len + `Only manager, account manager, business development representative, account executive, program manager, project manager, solution architect, or presales are able to join as ${targetRole}`, + ); err.status = 401; return next(err); } @@ -50,10 +98,22 @@ module.exports = [ } } else if (util.hasRoles(req, [USER_ROLE.MANAGER, USER_ROLE.CONNECT_ADMIN])) { targetRole = PROJECT_MEMBER_ROLE.MANAGER; - } else if (util.hasRoles(req, [USER_ROLE.TOPCODER_ACCOUNT_MANAGER])) { + } else if (util.hasRoles(req, [ + USER_ROLE.TOPCODER_ACCOUNT_MANAGER, + USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE, + USER_ROLE.PRESALES, + ])) { targetRole = PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER; } else if (util.hasRoles(req, [USER_ROLE.COPILOT, USER_ROLE.CONNECT_ADMIN])) { targetRole = PROJECT_MEMBER_ROLE.COPILOT; + } else if (util.hasRoles(req, [USER_ROLE.ACCOUNT_EXECUTIVE])) { + targetRole = PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE; + } else if (util.hasRoles(req, [USER_ROLE.PROGRAM_MANAGER])) { + targetRole = PROJECT_MEMBER_ROLE.PROGRAM_MANAGER; + } else if (util.hasRoles(req, [USER_ROLE.SOLUTION_ARCHITECT])) { + targetRole = PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT; + } else if (util.hasRoles(req, [USER_ROLE.PROJECT_MANAGER])) { + targetRole = PROJECT_MEMBER_ROLE.PROJECT_MANAGER; } else { const err = new Error('Only copilot or manager is able to call this endpoint'); err.status = 401; diff --git a/src/routes/projectMembers/update.js b/src/routes/projectMembers/update.js index 68aa0cc8..48ee069a 100644 --- a/src/routes/projectMembers/update.js +++ b/src/routes/projectMembers/update.js @@ -15,8 +15,17 @@ const permissions = tcMiddleware.permissions; const updateProjectMemberValdiations = { body: Joi.object().keys({ isPrimary: Joi.boolean(), - role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER, - PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.OBSERVER).required(), + role: Joi.any().valid( + PROJECT_MEMBER_ROLE.CUSTOMER, + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, + PROJECT_MEMBER_ROLE.COPILOT, + PROJECT_MEMBER_ROLE.OBSERVER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + ).required(), }), }; diff --git a/src/routes/projectReports/LookAuth.js b/src/routes/projectReports/LookAuth.js new file mode 100644 index 00000000..745d318f --- /dev/null +++ b/src/routes/projectReports/LookAuth.js @@ -0,0 +1,73 @@ +/* eslint-disable func-names */ +/* eslint-disable require-jsdoc */ +/* eslint-disable valid-jsdoc */ + +// Look Auth + + +import config from 'config'; + +const axios = require('axios'); + +const NEXT_5_MINS = 5 * 60 * 1000; + + +function LookAuth(logger) { + // load credentials from config + this.BASE_URL = config.lookerConfig.BASE_URL; + this.CLIENT_ID = config.lookerConfig.CLIENT_ID; + this.CLIENT_SECRET = config.lookerConfig.CLIENT_SECRET; + const token = config.lookerConfig.TOKEN; + + this.logger = logger; + + // Token is stringified and saved as string. It has 4 properties, access_token, expires_in and type, timestamp + if (token) { + this.lastToken = JSON.stringify(token); + } +} + +LookAuth.prototype.getToken = async function () { + const res = await new Promise((resolve) => { + if (!this.isExpired()) { + resolve(this.lastToken.access_token); + } else { + resolve(''); + } + }); + if (res === '') { + return this.login(); + } + return res; +}; + +/** *********************Login to Looker ************** */ +LookAuth.prototype.login = async function () { + const loginUrl = `${this.BASE_URL}/login?client_id=${this.CLIENT_ID}&client_secret=${this.CLIENT_SECRET}`; + const res = await axios.post(loginUrl, {}, { headers: { 'Content-Type': 'application/json' } }); + this.lastToken = res.data; + this.lastToken.timestamp = new Date().getTime(); + return this.lastToken.access_token; +}; + + +/** ***************Check if the Token has expired ********** */ +LookAuth.prototype.isExpired = function () { + // If no token is present, assume the token has expired + if (!this.lastToken) { + return true; + } + + const tokenTimestamp = this.lastToken.timestamp; + const expiresIn = this.lastToken.expires_in; + const currentTimestamp = new Date().getTime(); + + // If the token will good for next 5 minutes + if ((tokenTimestamp + expiresIn + NEXT_5_MINS) > currentTimestamp) { + return false; + } + // Token is good, and can be used to make the next call. + return true; +}; + +module.exports = LookAuth; diff --git a/src/routes/projectReports/LookRun.js b/src/routes/projectReports/LookRun.js new file mode 100644 index 00000000..f1606d88 --- /dev/null +++ b/src/routes/projectReports/LookRun.js @@ -0,0 +1,106 @@ +/* eslint-disable valid-jsdoc */ +/* eslint-disable require-jsdoc */ +/* eslint-disable func-names */ + +import config from 'config'; +import LookAuth from './LookAuth'; + +const axios = require('axios'); + +function LookApi(logger) { + this.BASE_URL = config.lookerConfig.BASE_URL; + this.formatting = 'json'; + this.limit = 5000; + this.logger = logger; + this.lookAuth = new LookAuth(logger); +} + +LookApi.prototype.runLook = function (lookId) { + const endpoint = `${this.BASE_URL}/looks/${lookId}/run/${this.formatting}?limit=${this.limit}`; + return this.callApi(endpoint); +}; + +LookApi.prototype.findUserByEmail = function (email) { + const filter = { 'user.email': email }; + return this.runQueryWithFilter(1234, filter); +}; + +LookApi.prototype.findByHandle = function (handle) { + const filter = { 'user.handle': handle }; + return this.runQueryWithFilter(12345, filter); +}; + +LookApi.prototype.findProjectRegSubmissions = function (projectId) { + const queryId = config.lookerConfig.QUERIES.REG_STATS; + const fields = ['connect_project.id', 'challenge.track', 'challenge.num_registrations', 'challenge.num_submissions']; + const view = 'challenge'; + const filters = { 'connect_project.id': projectId }; + return this.runQueryWithFilter(queryId, view, fields, filters); +}; + +LookApi.prototype.findProjectBudget = function (connectProjectId, permissions) { + const queryId = config.lookerConfig.QUERIES.BUDGET; + const { isManager, isAdmin, isCopilot, isCustomer } = permissions; + + const fields = [ + 'project_stream.tc_connect_project_id', + ]; + + // Manager roles have access to more fields. + if (isManager || isAdmin) { + fields.push('project_stream.total_actual_challenge_fee'); + } + if (isManager || isAdmin || isCopilot) { + fields.push('project_stream.total_actual_member_payment'); + } + if (isManager || isAdmin || isCustomer) { + fields.push('project_stream.total_invoiced_amount', 'project_stream.remaining_invoiced_budget'); + } + const view = 'project_stream'; + const filters = { 'project_stream.tc_connect_project_id': connectProjectId }; + return this.runQueryWithFilter(queryId, view, fields, filters); +}; + +LookApi.prototype.runQueryWithFilter = function (queryId, view, fields, filters) { + const endpoint = `${this.BASE_URL}/queries/run/${this.formatting}`; + + const body = { + id: queryId, + model: 'topcoder_model_main', + view, + filters, + fields, + // sorts: ['user.email desc 0'], + limit: 10, + query_timezon: 'America/Los_Angeles', + + }; + return this.callApi(endpoint, body); +}; + +LookApi.prototype.runQuery = function (queryId) { + const endpoint = `${this.BASE_URL}/queries/${queryId}/run/${this.formatting}?limit=${this.limit}`; + return this.callApi(endpoint); +}; + +LookApi.prototype.callApi = function (endpoint, body) { + return this.lookAuth.getToken().then((token) => { + let newReq = null; + if (body) { + newReq = axios.post(endpoint, body, { + headers: { + 'Content-Type': 'application/json', + Authorization: `token ${token}`, + }, + }); + } else { + newReq = axios.get(endpoint); + } + return newReq; + }).then((res) => { + this.logger.info(res.data); + return res.data; + }); +}; + +module.exports = LookApi; diff --git a/src/routes/projectReports/getReport.js b/src/routes/projectReports/getReport.js new file mode 100644 index 00000000..b2179c23 --- /dev/null +++ b/src/routes/projectReports/getReport.js @@ -0,0 +1,56 @@ +/* eslint-disable no-unused-vars */ +import config from 'config'; +import _ from 'lodash'; + +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import LookApi from './LookRun'; +import mock from './mock'; +import util from '../../util'; +import { PROJECT_MEMBER_MANAGER_ROLES, USER_ROLE, PROJECT_MEMBER_ROLE } from '../../constants'; + +const permissions = tcMiddleware.permissions; + + +module.exports = [ + permissions('project.view'), + async (req, res, next) => { + const projectId = Number(req.params.projectId); + const reportName = req.query.reportName; + + if (config.lookerConfig.USE_MOCK === 'true') { + req.log.info('using mock'); + // using mock + return mock(projectId, reportName, req, res); + // res.status(200).json(util.wrapResponse(req.id, project)); + } + const lookApi = new LookApi(req.log); + + try { + // check if auth user has acecss to this project + const members = req.context.currentProjectMembers; + const member = _.find(members, m => m.userId === req.authUser.userId); + const isManager = member && PROJECT_MEMBER_MANAGER_ROLES.indexOf(member.role) > -1; + const isAdmin = util.hasRoles(req, [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN]); + const isCopilot = member && member.role === PROJECT_MEMBER_ROLE.COPILOT; + const isCustomer = member && member.role === PROJECT_MEMBER_ROLE.CUSTOMER; + // pick the report based on its name + let result = {}; + switch (reportName) { + case 'summary': + result = await lookApi.findProjectRegSubmissions(projectId); + break; + case 'projectBudget': + result = await lookApi.findProjectBudget(projectId, { isManager, isAdmin, isCopilot, isCustomer }); + break; + default: + return res.status(404).send('Report not found'); + } + + req.log.debug(result); + return res.status(200).json(util.wrapResponse(req.id, result)); + } catch (err) { + req.log.error(err); + return res.status(500).send(err.toString()); + } + }, +]; diff --git a/src/routes/projectReports/mock.js b/src/routes/projectReports/mock.js new file mode 100644 index 00000000..33e6787d --- /dev/null +++ b/src/routes/projectReports/mock.js @@ -0,0 +1,25 @@ +import _ from 'lodash'; +import util from '../../util'; + +const summaryJson = require('./mockFiles/summary.json'); +let projectBudgetJson = require('./mockFiles/projectBudget.json'); + +module.exports = (projectId, reportName, req, res) => { + if (Number(projectId) === 123456) { + res.status(500).json('Invalid project id'); + } + + switch (reportName) { + case 'summary': + res.status(200).json(util.wrapResponse(req.id, summaryJson)); + break; + case 'projectBudget': { + const augmentProjectId = pb => _.assign(pb, { 'project_stream.tc_connect_project_id': projectId }); + projectBudgetJson = _.map(projectBudgetJson, augmentProjectId); + res.status(200).json(util.wrapResponse(req.id, projectBudgetJson)); + break; + } + default: + res.status(400).json('Invalid report name'); + } +}; diff --git a/src/routes/projectReports/mockFiles/projectBudget.json b/src/routes/projectReports/mockFiles/projectBudget.json new file mode 100644 index 00000000..7d8ec03a --- /dev/null +++ b/src/routes/projectReports/mockFiles/projectBudget.json @@ -0,0 +1,57 @@ +[ + { + "project_stream.tc_connect_project_id": "1", + "connect_project.directprojectid": "54321", + "connect_project.id": "12345", + "project_stream.id": "abcd", + "project_stream.total_approved_budget": "63680", + "project_stream.total_invoiced_amount": "43680", + "project_stream.remaining_invoiced_budget": "20000", + "project_stream.total_actual_challenge_fee": "29648.09", + "project_stream.total_actual_member_payment": "16351.91" + }, + { + "project_stream.tc_connect_project_id": "2", + "connect_project.directprojectid": "54321", + "connect_project.id": "12345", + "project_stream.id": "defg", + "project_stream.total_approved_budget": "10000", + "project_stream.total_invoiced_amount": "10000", + "project_stream.remaining_invoiced_budget": "0", + "project_stream.total_actual_challenge_fee": "0", + "project_stream.total_actual_member_payment": "0" + }, + { + "project_stream.tc_connect_project_id": "3", + "connect_project.directprojectid": "54321", + "connect_project.id": "12345", + "project_stream.id": "hijk", + "project_stream.total_approved_budget": "4,300", + "project_stream.total_invoiced_amount": "0", + "project_stream.remaining_invoiced_budget": "0", + "project_stream.total_actual_challenge_fee": "0", + "project_stream.total_actual_member_payment": "0" + }, + { + "project_stream.tc_connect_project_id": "4", + "connect_project.directprojectid": "54321", + "connect_project.id": "12345", + "project_stream.id": "klmn", + "project_stream.total_approved_budget": "1250", + "project_stream.total_invoiced_amount": "0", + "project_stream.remaining_invoiced_budget": "0", + "project_stream.total_actual_challenge_fee": "0", + "project_stream.total_actual_member_payment": "0" + }, + { + "project_stream.tc_connect_project_id": "5", + "connect_project.directprojectid": "54321", + "connect_project.id": "12345", + "project_stream.id": "opqu", + "project_stream.total_approved_budget": "0", + "project_stream.total_invoiced_amount": "0", + "project_stream.remaining_invoiced_budget": "0", + "project_stream.total_actual_challenge_fee": "0", + "project_stream.total_actual_member_payment": "0" + } +] \ No newline at end of file diff --git a/src/routes/projectReports/mockFiles/summary.json b/src/routes/projectReports/mockFiles/summary.json new file mode 100644 index 00000000..b97c9538 --- /dev/null +++ b/src/routes/projectReports/mockFiles/summary.json @@ -0,0 +1,17 @@ +[ { "challenge.track": "Develop", + + "challenge.num_registrations": 399, + + "challenge.num_submissions": 72 }, + + { "challenge.track": "Design", + + "challenge.num_registrations": 54, + + "challenge.num_submissions": 28 }, + + { "challenge.track": null, + + "challenge.num_registrations": 453, + + "challenge.num_submissions": 100 } ] \ No newline at end of file diff --git a/src/routes/projectSettings/create.js b/src/routes/projectSettings/create.js new file mode 100644 index 00000000..d2940f00 --- /dev/null +++ b/src/routes/projectSettings/create.js @@ -0,0 +1,94 @@ +/** + * API to add a project setting + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; +import models from '../../models'; +import { VALUE_TYPE } from '../../constants'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + }, + body: { + param: Joi.object().keys({ + key: Joi.string().max(255).required(), + value: Joi.string().max(255).required(), + valueType: Joi.string().valid(_.values(VALUE_TYPE)).required(), + projectId: Joi.any().strip(), + metadata: Joi.object().optional(), + readPermission: Joi.object().required(), + writePermission: Joi.object().required(), + }).required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('projectSetting.create'), + (req, res, next) => { + let setting = null; + const projectId = req.params.projectId; + const entity = _.assign(req.body.param, { + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + projectId, + }); + + // Check if project exists + models.sequelize.transaction(() => + models.Project.findOne({ where: { id: projectId } }) + .then((project) => { + if (!project) { + const apiErr = new Error(`Project not found for id ${projectId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + // Find project setting + return models.ProjectSetting.findOne({ + includeAllProjectSettingsForInternalUsage: true, + where: { + projectId, + key: req.body.param.key, + }, + paranoid: false, + }); + }) + .then((projectSetting) => { + if (projectSetting) { + const apiErr = new Error(`Project Setting already exists for project id ${projectId} ` + + `and key ${req.body.param.key}`); + apiErr.status = 400; + return Promise.reject(apiErr); + } + + // Create + return models.ProjectSetting.create(entity); + }) + .then(async (createdEntity) => { + setting = createdEntity; + // Calculate for valid estimation type + if (util.isProjectSettingForEstimation(createdEntity.key)) { + req.log.debug(`Recalculate price breakdown for project id ${projectId}`); + return util.calculateProjectEstimationItems(req, projectId); + } + + return Promise.resolve(); + }), + ) // transaction end + .then(() => { + req.log.debug('new project setting created (id# %d, key: %s)', + setting.id, setting.key); + // Omit deletedAt, deletedBy + res.status(201).json(util.wrapResponse( + req.id, _.omit(setting.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + }) + .catch(next); + }, +]; diff --git a/src/routes/projectSettings/create.spec.js b/src/routes/projectSettings/create.spec.js new file mode 100644 index 00000000..c1a14533 --- /dev/null +++ b/src/routes/projectSettings/create.spec.js @@ -0,0 +1,382 @@ +/** + * Tests for create.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import server from '../../app'; +import testUtil from '../../tests/util'; +import models from '../../models'; +import { VALUE_TYPE } from '../../constants'; + +const should = chai.should(); + +const expectAfterCreate = (id, projectId, estimation, len, deletedLen, err, next) => { + if (err) throw err; + + models.ProjectSetting.findOne({ + includeAllProjectSettingsForInternalUsage: true, + where: { + id, + projectId, + }, + }) + .then((res) => { + if (!res) { + throw new Error('Should found the entity'); + } else { + // find deleted ProjectEstimationItems for project + models.ProjectEstimationItem.findAllByProject(models, projectId, { + where: { + deletedAt: { $ne: null }, + }, + includeAllProjectEstimatinoItemsForInternalUsage: true, + paranoid: false, + }).then((items) => { + // deleted project estimation items + items.should.have.lengthOf(deletedLen, 'Number of deleted ProjectEstimationItems doesn\'t match'); + + _.each(items, (item) => { + should.exist(item.deletedBy); + should.exist(item.deletedAt); + }); + + // find (non-deleted) ProjectEstimationItems for project + return models.ProjectEstimationItem.findAllByProject(models, projectId, { + includeAllProjectEstimatinoItemsForInternalUsage: true, + }); + }).then((entities) => { + entities.should.have.lengthOf(len, 'Number of created ProjectEstimationItems doesn\'t match'); + if (len) { + entities[0].projectEstimationId.should.be.eql(estimation.id); + if (estimation.valueType === VALUE_TYPE.PERCENTAGE) { + entities[0].price.should.be.eql((estimation.price * estimation.value) / 100); + } else { + entities[0].price.should.be.eql(Number(estimation.value)); + } + entities[0].type.should.be.eql(estimation.key.split('markup_')[1]); + entities[0].markupUsedReference.should.be.eql('projectSetting'); + entities[0].markupUsedReferenceId.should.be.eql(id); + should.exist(entities[0].updatedAt); + should.not.exist(entities[0].deletedBy); + should.not.exist(entities[0].deletedAt); + } + + next(); + }).catch(next); + } + }); +}; + +describe('CREATE Project Setting', () => { + let projectId; + let estimationId; + + const body = { + param: { + key: 'markup_topcoder_service', + value: '3500', + valueType: 'double', + readPermission: { + projectRoles: ['customer'], + topcoderRoles: ['administrator'], + }, + writePermission: { + allowRule: { topcoderRoles: ['administrator'] }, + denyRule: { projectRoles: ['copilot'] }, + }, + }, + }; + + const estimation = { + buildingBlockKey: 'BLOCK_KEY', + conditions: '( HAS_DEV_DELIVERABLE && ONLY_ONE_OS_MOBILE && CA_NEEDED )', + price: 5000, + quantity: 10, + minTime: 35, + maxTime: 35, + metadata: { + deliverable: 'dev-qa', + }, + createdBy: 1, + updatedBy: 1, + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + + models.ProjectEstimation.create(_.assign(estimation, { projectId })) + .then((e) => { + estimationId = e.id; + done(); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('POST /projects/{projectId}/settings', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .post(`/v5/projects/${projectId}/settings`) + .send(body) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 404 for non-existed project', (done) => { + request(server) + .post('/v5/projects/9999/settings') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(404, done); + }); + + it('should return 400 for missing key', (done) => { + const invalidBody = _.cloneDeep(body); + delete invalidBody.param.key; + + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 for missing value', (done) => { + const invalidBody = _.cloneDeep(body); + delete invalidBody.param.value; + + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 for missing valueType', (done) => { + const invalidBody = _.cloneDeep(body); + delete invalidBody.param.valueType; + + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + xit('should return 400 for negative value when valueType = percentage', (done) => { + const invalidBody = _.cloneDeep(body); + invalidBody.param.value = '-10'; + invalidBody.param.valueType = VALUE_TYPE.PERCENTAGE; + + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + xit('should return 400 for value greater than 100 when valueType = percentage', (done) => { + const invalidBody = _.cloneDeep(body); + invalidBody.param.value = '150'; + invalidBody.param.valueType = VALUE_TYPE.PERCENTAGE; + + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400, for admin, when create key with existing key', (done) => { + const existing = _.cloneDeep(body); + existing.param.projectId = projectId; + existing.param.createdBy = 1; + existing.param.updatedBy = 1; + + models.ProjectSetting.create(existing.param).then(() => { + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(400, done); + }).catch(done); + }); + + it('should return 201 for manager with non-estimation type, not calculating project estimation items', + (done) => { + const createBody = _.cloneDeep(body); + createBody.param.key = 'markup_no_estimation'; + + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(createBody) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) done(err); + + const resJson = res.body.result.content; + resJson.key.should.be.eql(createBody.param.key); + resJson.value.should.be.eql(createBody.param.value); + resJson.valueType.should.be.eql(createBody.param.valueType); + resJson.projectId.should.be.eql(projectId); + resJson.createdBy.should.be.eql(40051334); + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(40051334); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + expectAfterCreate(resJson.id, projectId, null, 0, 0, err, done); + }); + }); + + it('should return 201 for manager, calculating project estimation items', (done) => { + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) done(err); + + const resJson = res.body.result.content; + resJson.key.should.be.eql(body.param.key); + resJson.value.should.be.eql(body.param.value); + resJson.valueType.should.be.eql(body.param.valueType); + resJson.projectId.should.be.eql(projectId); + resJson.createdBy.should.be.eql(40051334); + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(40051334); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + expectAfterCreate(resJson.id, projectId, _.assign(estimation, { + id: estimationId, + value: body.param.value, + valueType: body.param.valueType, + key: body.param.key, + }), 1, 0, err, done); + }); + }); + + it('should return 201 for admin', (done) => { + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) done(err); + + const resJson = res.body.result.content; + resJson.key.should.be.eql(body.param.key); + resJson.value.should.be.eql(body.param.value); + resJson.valueType.should.be.eql(body.param.valueType); + resJson.projectId.should.be.eql(projectId); + resJson.createdBy.should.be.eql(40051333); + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(40051333); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + done(); + }); + }); + + it('should return 201 for connect admin', (done) => { + request(server) + .post(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) done(err); + + const resJson = res.body.result.content; + resJson.key.should.be.eql(body.param.key); + resJson.value.should.be.eql(body.param.value); + resJson.valueType.should.be.eql(body.param.valueType); + resJson.projectId.should.be.eql(projectId); + resJson.createdBy.should.be.eql(40051336); + resJson.updatedBy.should.be.eql(40051336); + should.exist(resJson.createdAt); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + done(); + }); + }); + }); +}); diff --git a/src/routes/projectSettings/delete.js b/src/routes/projectSettings/delete.js new file mode 100644 index 00000000..aa85f493 --- /dev/null +++ b/src/routes/projectSettings/delete.js @@ -0,0 +1,63 @@ +/** + * API to delete a project setting + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('projectSetting.delete'), + (req, res, next) => { + const projectId = req.params.projectId; + const id = req.params.id; + let deletedEntity = null; + + models.sequelize.transaction(() => + models.ProjectSetting.findOne({ + includeAllProjectSettingsForInternalUsage: true, + where: { + id, + projectId, + }, + }) + .then((entity) => { + // Not found + if (!entity) { + const apiErr = new Error(`Project setting not found for id ${id} and project id ${projectId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + deletedEntity = entity; + // Update the deletedBy, then delete + return entity.update({ deletedBy: req.authUser.userId }); + }) + .then(entity => entity.destroy()) + .then(() => { + // Calculate for valid estimation type + if (util.isProjectSettingForEstimation(deletedEntity.key)) { + req.log.debug(`Recalculate price breakdown for project id ${projectId}`); + return util.calculateProjectEstimationItems(req, projectId); + } + + return Promise.resolve(); + }), + ) // transaction end + .then(() => { + res.status(204).end(); + }) + .catch(next); + }, +]; diff --git a/src/routes/projectSettings/delete.spec.js b/src/routes/projectSettings/delete.spec.js new file mode 100644 index 00000000..78d1e514 --- /dev/null +++ b/src/routes/projectSettings/delete.spec.js @@ -0,0 +1,281 @@ +/** + * Tests for delete.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import server from '../../app'; +import testUtil from '../../tests/util'; +import models from '../../models'; + +const should = chai.should(); + +const expectAfterDelete = (id, projectId, len, deletedLen, err, next) => { + if (err) throw err; + + models.ProjectSetting.findOne({ + includeAllProjectSettingsForInternalUsage: true, + where: { + id, + projectId, + }, + paranoid: false, + }) + .then((res) => { + if (!res) { + throw new Error('Should found the entity'); + } else { + should.exist(res.deletedBy); + should.exist(res.deletedAt); + + // find deleted ProjectEstimationItems for project + models.ProjectEstimationItem.findAllByProject(models, projectId, { + where: { + deletedAt: { $ne: null }, + }, + includeAllProjectEstimatinoItemsForInternalUsage: true, + paranoid: false, + }).then((items) => { + // deleted project estimation items + items.should.have.lengthOf(deletedLen, 'Number of deleted ProjectEstimationItems doesn\'t match'); + _.each(items, (item) => { + should.exist(item.deletedBy); + should.exist(item.deletedAt); + }); + + // find (non-deleted) ProjectEstimationItems for project + return models.ProjectEstimationItem.findAllByProject(models, projectId, { + includeAllProjectEstimatinoItemsForInternalUsage: true, + }); + }).then((items) => { + // all non-deleted project estimation item count + items.should.have.lengthOf(len, 'Number of created ProjectEstimationItems doesn\'t match'); + next(); + }).catch(next); + } + }); +}; + +describe('DELETE Project Setting', () => { + let projectId; + let estimationId; + let id; + let id2; + + const estimation = { + buildingBlockKey: 'BLOCK_KEY', + conditions: '( HAS_DEV_DELIVERABLE && SCREENS_COUNT_SMALL && CA_NEEDED)', + price: 6500.50, + quantity: 10, + minTime: 35, + maxTime: 35, + metadata: { + deliverable: 'dev-qa', + }, + createdBy: 1, + updatedBy: 1, + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + + models.ProjectSetting.bulkCreate([{ + projectId, + key: 'markup_topcoder_service', + value: '5599.96', + valueType: 'double', + readPermission: { + projectRoles: ['customer'], + topcoderRoles: ['administrator'], + }, + writePermission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['administrator'], + }, + denyRule: { + projectRoles: ['copilot'], + }, + }, + createdBy: 1, + updatedBy: 1, + }, { + projectId, + key: 'markup_no_estimation', + value: '40', + valueType: 'percentage', + readPermission: { + topcoderRoles: ['administrator'], + }, + writePermission: { + allowRule: { topcoderRoles: ['administrator'] }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }], { returning: true }) + .then((settings) => { + id = settings[0].id; + id2 = settings[1].id; + models.ProjectEstimation.create(_.assign(estimation, { projectId })) + .then((e) => { + estimationId = e.id; + done(); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('DELETE /projects/{projectId}/settings/{id}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/settings/${id}`) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 404 for non-existed project', (done) => { + request(server) + .delete(`/v5/projects/9999/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 for non-existed project setting', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/settings/1234`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 for deleted project setting', (done) => { + models.ProjectSetting.destroy({ where: { id } }) + .then(() => { + request(server) + .delete(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }).catch(done); + }); + + it('should return 204, for admin, if project setting was successfully removed', (done) => { + models.ProjectEstimationItem.create({ + projectEstimationId: estimationId, + price: 1200, + type: 'topcoder_service', + markupUsedReference: 'projectSetting', + markupUsedReferenceId: id, + createdBy: 1, + updatedBy: 1, + }) + .then(() => { + request(server) + .delete(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end(err => expectAfterDelete(id, projectId, 0, 1, err, done)); + }).catch(done); + }); + + it('should return 204, for admin, if project setting with non-estimation type was successfully removed', + (done) => { + request(server) + .delete(`/v5/projects/${projectId}/settings/${id2}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end(err => expectAfterDelete(id2, projectId, 0, 0, err, done)); + }); + + it('should return 204, for admin, another project setting exists if the project setting was successfully removed', + (done) => { + models.ProjectSetting.create({ + projectId, + key: 'markup_fee', + value: '25', + valueType: 'percentage', + readPermission: { + projectRoles: ['customer'], + topcoderRoles: ['administrator'], + }, + writePermission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['administrator'], + }, + denyRule: { + projectRoles: ['copilot'], + }, + }, + createdBy: 1, + updatedBy: 1, + }).then((anotherSetting) => { + models.ProjectEstimationItem.create({ + projectEstimationId: estimationId, + price: 1200, + type: 'fee', + markupUsedReference: 'projectSetting', + markupUsedReferenceId: anotherSetting.id, + createdBy: 1, + updatedBy: 1, + }).then(() => { + request(server) + .delete(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end(err => expectAfterDelete(id, projectId, 1, 1, err, done)); + }); + }).catch(done); + }); + }); +}); diff --git a/src/routes/projectSettings/list.js b/src/routes/projectSettings/list.js new file mode 100644 index 00000000..1668178f --- /dev/null +++ b/src/routes/projectSettings/list.js @@ -0,0 +1,51 @@ +/** + * API to list project setting + */ +import _ from 'lodash'; +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; + +const permissions = tcMiddleware.permissions; + +const schema = { + query: { + includeAllProjectSettingsForInternalUsage: Joi.boolean().optional(), + }, +}; + +module.exports = [ + validate(schema), + permissions('projectSetting.view'), + (req, res, next) => { + const projectId = req.params.projectId; + const options = { + where: { + projectId, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + // provide current user and project members list so `ProjectSetting.findAll` will return + // only records available to view by the current user + reqUser: req.authUser, + members: req.context.currentProjectMembers, + }; + + models.Project.findOne({ where: { id: projectId } }) + .then((project) => { + if (!project) { + const apiErr = new Error(`Project not found for id ${projectId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + return models.ProjectSetting.findAll(options); + }) + .then((result) => { + res.json(util.wrapResponse(req.id, _.filter(result, r => r))); + }) + .catch(next); + }, +]; diff --git a/src/routes/projectSettings/list.spec.js b/src/routes/projectSettings/list.spec.js new file mode 100644 index 00000000..08535645 --- /dev/null +++ b/src/routes/projectSettings/list.spec.js @@ -0,0 +1,243 @@ +/** + * Tests for list.js + */ +import _ from 'lodash'; +import request from 'supertest'; +import chai from 'chai'; +import server from '../../app'; +import models from '../../models'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('LIST Project Settings', () => { + let projectId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + const settings = [{ + key: 'markup_topcoder_service', + value: '3500', + valueType: 'double', + readPermission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['administrator'], + }, + denyRule: { + projectRoles: ['copilot'], + topcoderRoles: ['Connect Admin'], + }, + }, + writePermission: { + allowRule: { topcoderRoles: ['administrator'] }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }, { + key: 'markup_fee', + value: '15', + valueType: 'percentage', + readPermission: { + topcoderRoles: ['administrator'], + }, + writePermission: { + allowRule: { topcoderRoles: ['administrator'] }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }]; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]) + .then(() => { + models.ProjectSetting.bulkCreate(_.map(settings, s => _.assign(s, { projectId }))).then(() => done()); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/settings', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get(`/v5/projects/${projectId}/settings`) + .expect(403, done); + }); + + it('should return 403 when user have no permission (non team member)', (done) => { + request(server) + .get(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .expect(403, done); + }); + + it('should return 404 for deleted project', (done) => { + models.Project.destroy({ where: { id: projectId } }) + .then(() => { + request(server) + .get(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + }); + + it('should return 404 for non-existed project', (done) => { + request(server) + .get('/v5/projects/99999/settings') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 0 setting when copilot has readPermission for both denyRule and allowRule', (done) => { + request(server) + .get(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.should.have.lengthOf(0); + done(); + } + }); + }); + + it('should return 0 setting when connect admin has readPermission for denyRule', (done) => { + request(server) + .get(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.should.have.lengthOf(0); + done(); + } + }); + }); + + it('should return 1 setting when user have readPermission (customer)', (done) => { + request(server) + .get(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.should.have.lengthOf(1); + const setting = settings[0]; + resJson[0].key.should.be.eql(setting.key); + resJson[0].value.should.be.eql(setting.value); + resJson[0].valueType.should.be.eql(setting.valueType); + resJson[0].projectId.should.be.eql(projectId); + resJson[0].readPermission.should.be.eql(setting.readPermission); + resJson[0].writePermission.should.be.eql(setting.writePermission); + done(); + } + }); + }); + + it('should return 2 settings when user have readPermission (administrator)', (done) => { + request(server) + .get(`/v5/projects/${projectId}/settings`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body.result.content; + should.exist(resJson); + resJson.should.have.lengthOf(2); + const setting = settings[0]; + resJson[0].key.should.be.eql(setting.key); + resJson[0].value.should.be.eql(setting.value); + resJson[0].valueType.should.be.eql(setting.valueType); + resJson[0].projectId.should.be.eql(projectId); + resJson[0].readPermission.should.be.eql(setting.readPermission); + resJson[0].writePermission.should.be.eql(setting.writePermission); + done(); + } + }); + }); + }); +}); diff --git a/src/routes/projectSettings/update.js b/src/routes/projectSettings/update.js new file mode 100644 index 00000000..df9504c4 --- /dev/null +++ b/src/routes/projectSettings/update.js @@ -0,0 +1,76 @@ +/** + * API to update a project setting + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; +import models from '../../models'; +import { VALUE_TYPE } from '../../constants'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, + body: { + param: Joi.object().keys({ + value: Joi.string().max(255), + valueType: Joi.string().valid(_.values(VALUE_TYPE)), + projectId: Joi.any().strip(), + metadata: Joi.object(), + readPermission: Joi.object(), + writePermission: Joi.object(), + }).required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('projectSetting.edit'), + (req, res, next) => { + let oldKey = null; + let updatedSetting = null; + const projectId = req.params.projectId; + const id = req.params.id; + const entityToUpdate = _.assign(req.body.param, { + updatedBy: req.authUser.userId, + }); + + models.sequelize.transaction(() => + models.ProjectSetting.findOne({ + includeAllProjectSettingsForInternalUsage: true, + where: { + id, + projectId, + }, + }) + .then((existing) => { + // Not found + if (!existing) { + const apiErr = new Error(`Project setting not found for id ${id} and project id ${projectId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + oldKey = existing.key; + return existing.update(entityToUpdate); + }) + .then((updated) => { + updatedSetting = updated; + if (util.isProjectSettingForEstimation(updatedSetting.key) || util.isProjectSettingForEstimation(oldKey)) { + req.log.debug(`Recalculate price breakdown for project id ${projectId}`); + return util.calculateProjectEstimationItems(req, projectId); + } + return Promise.resolve(); + }), + ) // transaction end + .then(() => { + res.json(util.wrapResponse(req.id, updatedSetting)); + }) + .catch(next); + }, +]; diff --git a/src/routes/projectSettings/update.spec.js b/src/routes/projectSettings/update.spec.js new file mode 100644 index 00000000..fb3c2856 --- /dev/null +++ b/src/routes/projectSettings/update.spec.js @@ -0,0 +1,418 @@ +/** + * Tests for update.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import server from '../../app'; +import testUtil from '../../tests/util'; +import models from '../../models'; +import { VALUE_TYPE } from '../../constants'; + +const should = chai.should(); + +const expectAfterUpdate = (id, projectId, estimation, len, deletedLen, err, next) => { + if (err) throw err; + + models.ProjectSetting.findOne({ + includeAllProjectSettingsForInternalUsage: true, + where: { + id, + projectId, + }, + }) + .then((res) => { + if (!res) { + throw new Error('Should found the entity'); + } else { + // find deleted ProjectEstimationItems for project + models.ProjectEstimationItem.findAllByProject(models, projectId, { + where: { + deletedAt: { $ne: null }, + }, + includeAllProjectEstimatinoItemsForInternalUsage: true, + paranoid: false, + }).then((items) => { + // deleted project estimation items + items.should.have.lengthOf(deletedLen, 'Number of deleted ProjectEstimationItems doesn\'t match'); + + _.each(items, (item) => { + should.exist(item.deletedBy); + should.exist(item.deletedAt); + }); + + // find (non-deleted) ProjectEstimationItems for project + return models.ProjectEstimationItem.findAllByProject(models, projectId, { + includeAllProjectEstimatinoItemsForInternalUsage: true, + }); + }).then((entities) => { + entities.should.have.lengthOf(len, 'Number of created ProjectEstimationItems doesn\'t match'); + if (len) { + entities[0].projectEstimationId.should.be.eql(estimation.id); + if (estimation.valueType === VALUE_TYPE.PERCENTAGE) { + entities[0].price.should.be.eql((estimation.price * estimation.value) / 100); + } else { + entities[0].price.should.be.eql(Number(estimation.value)); + } + entities[0].type.should.be.eql(estimation.key.split('markup_')[1]); + entities[0].markupUsedReference.should.be.eql('projectSetting'); + entities[0].markupUsedReferenceId.should.be.eql(id); + should.exist(entities[0].updatedAt); + should.not.exist(entities[0].deletedBy); + should.not.exist(entities[0].deletedAt); + } + + next(); + }); + } + }); +}; + +describe('UPDATE Project Setting', () => { + let projectId; + let estimationId; + let id; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + const body = { + param: { + value: '5599.96', + valueType: 'double', + readPermission: { + projectRoles: ['customer'], + topcoderRoles: ['administrator'], + }, + writePermission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['administrator', 'Connect Admin'], + }, + denyRule: { + projectRoles: ['copilot'], + topcoderRoles: ['Connect Admin'], + }, + }, + }, + }; + + // we don't include these params into the body, we cannot update them + // but we use them for creating model directly and for checking returned values + const bodyParamNonMutable = { + key: 'markup_topcoder_service', + createdBy: 1, + updatedBy: 1, + }; + + const estimation = { + buildingBlockKey: 'BLOCK_KEY', + conditions: '( HAS_DEV_DELIVERABLE && ONLY_ONE_OS_MOBILE && CA_NEEDED )', + price: 6500.50, + quantity: 10, + minTime: 35, + maxTime: 35, + metadata: { + deliverable: 'dev-qa', + }, + createdBy: 1, + updatedBy: 1, + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]) + .then(() => { + models.ProjectSetting.create(_.assign({}, body.param, bodyParamNonMutable, { + projectId, + })) + .then((s) => { + id = s.id; + + models.ProjectEstimation.create(_.assign(estimation, { projectId })) + .then((e) => { + estimationId = e.id; + done(); + }); + }).catch(done); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('PATCH /projects/{projectId}/settings/{id}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/settings/${id}`) + .send(body) + .expect(403, done); + }); + + it('should return 403 when user have no permission (non team member)', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 403 when copilot is in both denyRule and allowRule', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 403 when connect admin is in both denyRule and allowRule', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 404 for non-existed project', (done) => { + request(server) + .patch(`/v5/projects/9999/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(404, done); + }); + + it('should return 404 for non-existed project setting', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/settings/1234`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(404, done); + }); + + it('should return 404 for deleted project setting', (done) => { + models.ProjectSetting.destroy({ where: { id } }) + .then(() => { + request(server) + .patch(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(404, done); + }); + }); + + it('should return 400, when try to update key', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + param: { + key: 'updated_key', + }, + }) + .expect(400, done); + }); + + it('should return 200, for member with permission (team member), value updated but no project estimation present', + (done) => { + const notPresent = _.cloneDeep(body); + notPresent.param.value = '4500'; + + models.ProjectEstimation.destroy({ + where: { + id: estimationId, + }, + }).then(() => { + models.ProjectEstimationItem.destroy({ + where: { + markupUsedReference: 'projectSetting', + markupUsedReferenceId: id, + }, + }).then(() => { + request(server) + .patch(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + param: { + value: notPresent.param.value, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) done(err); + + const resJson = res.body.result.content; + resJson.id.should.be.eql(id); + resJson.key.should.be.eql(bodyParamNonMutable.key); + resJson.value.should.be.eql(notPresent.param.value); + resJson.valueType.should.be.eql(notPresent.param.valueType); + resJson.projectId.should.be.eql(projectId); + resJson.createdBy.should.be.eql(bodyParamNonMutable.createdBy); + resJson.updatedBy.should.be.eql(40051331); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + expectAfterUpdate(id, projectId, _.assign(estimation, { + id: estimationId, + value: notPresent.param.value, + valueType: notPresent.param.valueType, + key: bodyParamNonMutable.key, + }), 0, 0, err, done); + }); + }); + }).catch(done); + }); + + it('should return 200 for admin when value updated, calculating project estimation items', (done) => { + body.param.value = '4500'; + + models.ProjectEstimationItem.create({ + projectEstimationId: estimationId, + price: 1200, + type: 'topcoder_service', + markupUsedReference: 'projectSetting', + markupUsedReferenceId: id, + createdBy: 1, + updatedBy: 1, + }).then(() => { + request(server) + .patch(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + param: { + value: body.param.value, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) done(err); + + const resJson = res.body.result.content; + resJson.id.should.be.eql(id); + resJson.key.should.be.eql(bodyParamNonMutable.key); + resJson.value.should.be.eql(body.param.value); + resJson.valueType.should.be.eql(body.param.valueType); + resJson.projectId.should.be.eql(projectId); + resJson.createdBy.should.be.eql(bodyParamNonMutable.createdBy); + resJson.updatedBy.should.be.eql(40051333); // admin + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + expectAfterUpdate(id, projectId, _.assign(estimation, { + id: estimationId, + value: body.param.value, + valueType: body.param.valueType, + key: bodyParamNonMutable.key, + }), 1, 1, err, done); + }); + }).catch(done); + }); + + it('should return 200, for admin, update valueType from double to percentage', (done) => { + body.param.value = '10.76'; + body.param.valueType = VALUE_TYPE.PERCENTAGE; + request(server) + .patch(`/v5/projects/${projectId}/settings/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + param: { + value: body.param.value, + valueType: VALUE_TYPE.PERCENTAGE, + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) done(err); + + const resJson = res.body.result.content; + resJson.id.should.be.eql(id); + resJson.key.should.be.eql(bodyParamNonMutable.key); + resJson.value.should.be.eql(body.param.value); + resJson.valueType.should.be.eql(VALUE_TYPE.PERCENTAGE); + resJson.projectId.should.be.eql(projectId); + resJson.createdBy.should.be.eql(bodyParamNonMutable.createdBy); + resJson.updatedBy.should.be.eql(40051333); // admin + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + expectAfterUpdate(id, projectId, _.assign(estimation, { + id: estimationId, + value: body.param.value, + valueType: body.param.valueType, + key: bodyParamNonMutable.key, + }), 1, 0, err, done); + }); + }); + }); +}); diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js index 40278d22..4827122c 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 } from '../../constants'; + EVENT, RESOURCES, REGEX, WORKSTREAM_STATUS } from '../../constants'; import fieldLookupValidation from '../../middlewares/fieldLookupValidation'; import util from '../../util'; @@ -37,6 +37,10 @@ const createProjectValdiations = { bookmarks: Joi.array().items(Joi.object().keys({ title: Joi.string(), address: Joi.string().regex(REGEX.URL), + createdAt: Joi.date(), + createdBy: Joi.number().integer().positive(), + updatedAt: Joi.date(), + updatedBy: Joi.number().integer().positive(), })).optional().allow(null), estimatedPrice: Joi.number().precision(2).positive().optional() .allow(null), @@ -64,18 +68,117 @@ const createProjectValdiations = { buildingBlockKey: Joi.string().required(), metadata: Joi.object().optional(), })).optional(), + attachments: Joi.array().items(Joi.object().keys({ + category: Joi.string().required(), + contentType: Joi.string().required(), + description: Joi.string().allow(null).allow('').optional(), + filePath: Joi.string().required(), + size: Joi.number().required(), + title: Joi.string().required(), + })).optional(), }).required(), }; +/** + * Create ProjectEstimationItem with BuildingBlock. + * @param {Array} estimations the project estimations + * @param {Number} userId the request user id + * @returns {Promise} the promise that resolves to the created ProjectEstimationItem + */ +function createEstimationItemsWithBuildingBlock(estimations, userId) { + const buildingBlockKeys = _.map(estimations, estimation => estimation.buildingBlockKey); + // get all building blocks + return models.BuildingBlock.findAll({ + where: { deletedAt: { $eq: null }, key: buildingBlockKeys }, + raw: true, + includePrivateConfigForInternalUsage: true, + }).then((buildingBlocks) => { + const blocks = {}; + _.forEach(buildingBlocks, (block) => { + if (block) { + blocks[block.key] = block; + } + }); + const estimationItems = []; + _.forEach(estimations, (estimation) => { + const block = blocks[estimation.buildingBlockKey]; + if (block && _.get(block, 'privateConfig.priceItems')) { + _.forOwn(block.privateConfig.priceItems, (item, key) => { + let itemPrice; + if (_.isString(item) && item.endsWith('%')) { + const percent = _.toNumber(item.replace('%', '')) / 100; + itemPrice = _.toNumber(estimation.price) * percent; + } else { + itemPrice = item; + } + estimationItems.push({ + projectEstimationId: estimation.id, + price: itemPrice, + type: key, + markupUsedReference: 'buildingBlock', + markupUsedReferenceId: block.id, + createdBy: userId, + updatedBy: userId, + }); + }); + } + }); + + return models.ProjectEstimationItem.bulkCreate(estimationItems, { returning: true }); + }); +} + +/** + * Create workstreams for newly created project based on provided workstreams config + * and project details + * + * @param {Object} req express request object + * @param {Object} newProject new created project + * @param {Object} workstreamsConfig config of workstreams to create + * + * @returns {Promise} the list of created WorkStreams + */ +function createWorkstreams(req, newProject, workstreamsConfig) { + if (!workstreamsConfig) { + req.log.debug('no workstream config found'); + return Promise.resolve([]); + } + + req.log.debug('creating project workstreams'); + + // get value of the field in the project data which would determine which workstream types to create + const projectFieldValue = _.get(newProject, workstreamsConfig.projectFieldName); + + // the list of workstream types to create, based on the project field values + // mapping provided in `workstreamTypesToProjectValues` + const workstreamTypesToCreate = _.keys(_.pickBy(workstreamsConfig.workstreamTypesToProjectValues, fieldValues => ( + _.intersection(fieldValues, projectFieldValue).length > 0 + ))); + + // the list workstreams to create + const workstreamsToCreate = _.filter(workstreamsConfig.workstreams, workstream => ( + _.includes(workstreamTypesToCreate, workstream.type) + )).map(workstreamToCreate => _.assign({}, workstreamToCreate, { + projectId: newProject.id, + status: WORKSTREAM_STATUS.DRAFT, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + })); + + return models.WorkStream.bulkCreate(workstreamsToCreate); + // return Promise.resolve(workstreamsToCreate); +} + /** * Create the project, project phases and products. This needs to be done before creating direct project. * @param {Object} req the request * @param {Object} project the project * @param {Object} projectTemplate the project template - * @param {Array} productTemplates array of the templates of the products used in the projec template + * @param {Array} productTemplates array of the templates of the products used in the project template + * @param {Array} phasesList list phases definitions to create * @returns {Promise} the promise that resolves to the created project and phases */ -function createProjectAndPhases(req, project, projectTemplate, productTemplates) { +function createProjectAndPhases(req, project, projectTemplate, productTemplates, phasesList) { const result = { newProject: null, newPhases: [], @@ -103,63 +206,94 @@ function createProjectAndPhases(req, project, projectTemplate, productTemplates) } return Promise.resolve(newProject); }).then((newProject) => { + req.log.debug('creating project estimation items with building blocks'); + if (result.estimations && result.estimations.length > 0) { + return createEstimationItemsWithBuildingBlock(result.estimations, req.authUser.userId) + .then((estimationItems) => { + req.log.debug(`creating ${estimationItems.length} project estimation items`); + // ignore project estimation items for now + return Promise.resolve(newProject); + }); + } + return Promise.resolve(newProject); + }).then((newProject) => { + if (project.attachments && (project.attachments.length > 0)) { + req.log.debug('creating project attachments'); + const attachments = project.attachments.map(attachment => Object.assign({ + projectId: newProject.id, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }, attachment)); + return models.ProjectAttachment.bulkCreate(attachments, { returning: true }).then((projectAttachments) => { + result.attachments = _.map(projectAttachments, attachment => + _.omit(attachment.toJSON(), ['deletedAt', 'deletedBy'])); + return Promise.resolve(newProject); + }); + } + return Promise.resolve(newProject); + }) + .then((newProject) => { result.newProject = newProject; // backward compatibility for releasing the service before releasing the front end if (!projectTemplate) { return Promise.resolve(result); } - const phases = _.filter(_.values(projectTemplate.phases), p => !!p); const productTemplateMap = {}; productTemplates.forEach((pt) => { productTemplateMap[pt.id] = pt; }); - return Promise.all(_.map(phases, (phase, phaseIdx) => { - const duration = _.get(phase, 'duration', 1); - const startDate = moment.utc().hours(0).minutes(0).seconds(0) - .milliseconds(0); - // Create phase - return models.ProjectPhase.create({ - projectId: newProject.id, - name: _.get(phase, 'name', `Stage ${phaseIdx}`), - duration, - startDate: startDate.format(), - endDate: moment.utc(startDate).add(duration - 1, 'days').format(), - status: _.get(phase, 'status', PROJECT_PHASE_STATUS.DRAFT), - budget: _.get(phase, 'budget', 0), - updatedBy: req.authUser.userId, - createdBy: req.authUser.userId, - }).then((newPhase) => { - req.log.debug(`Creating products in the newly created phase ${newPhase.id}`); - // Create products - return models.PhaseProduct.bulkCreate(_.map(phase.products, (product, productIndex) => ({ - phaseId: newPhase.id, + + if (phasesList) { + return Promise.all(_.map(phasesList, (phase, phaseIdx) => { + const duration = _.get(phase, 'duration', 1); + const startDate = moment.utc().hours(0).minutes(0).seconds(0) + .milliseconds(0); + // Create phase + return models.ProjectPhase.create({ projectId: newProject.id, - estimatedPrice: _.get(product, 'estimatedPrice', 0), - name: _.get(product, 'name', _.get(productTemplateMap, `${product.id}.name`, `Product ${productIndex}`)), - // assumes that phase template always contains id of each product - templateId: parseInt(product.id, 10), + name: _.get(phase, 'name', `Stage ${phaseIdx}`), + duration, + startDate: startDate.format(), + endDate: moment.utc(startDate).add(duration - 1, 'days').format(), + status: _.get(phase, 'status', PROJECT_PHASE_STATUS.DRAFT), + budget: _.get(phase, 'budget', 0), updatedBy: req.authUser.userId, createdBy: req.authUser.userId, - })), { returning: true }) - .then((products) => { - // Add phases and products to the project JSON, so they can be stored to ES later - const newPhaseJson = _.omit(newPhase.toJSON(), ['deletedAt', 'deletedBy']); - newPhaseJson.products = _.map(products, product => - _.omit(product.toJSON(), ['deletedAt', 'deletedBy'])); - result.newPhases.push(newPhaseJson); - return Promise.resolve(); + }).then((newPhase) => { + req.log.debug(`Creating products in the newly created phase ${newPhase.id}`); + // Create products + return models.PhaseProduct.bulkCreate(_.map(phase.products, (product, productIndex) => ({ + phaseId: newPhase.id, + projectId: newProject.id, + estimatedPrice: _.get(product, 'estimatedPrice', 0), + name: _.get(product, 'name', _.get(productTemplateMap, `${product.id}.name`, `Product ${productIndex}`)), + // assumes that phase template always contains id of each product + templateId: parseInt(product.id, 10), + updatedBy: req.authUser.userId, + createdBy: req.authUser.userId, + })), { returning: true }) + .then((products) => { + // Add phases and products to the project JSON, so they can be stored to ES later + const newPhaseJson = _.omit(newPhase.toJSON(), ['deletedAt', 'deletedBy']); + newPhaseJson.products = _.map(products, product => + _.omit(product.toJSON(), ['deletedAt', 'deletedBy'])); + result.newPhases.push(newPhaseJson); + return Promise.resolve(); + }); }); - }); - })); - }).then(() => Promise.resolve(result)); + })); + } + return Promise.resolve(); + }) + .then(() => Promise.resolve(result)); } /** * Validates the project and product templates for the give project template id. * * @param {Integer} templateId id of the project template which should be validated - * @returns {Promise} the promise that resolves to an object containing validated project and product templates + * @returns {Promise} the promise that resolves to an object containing validated project, product templates and phases list */ function validateAndFetchTemplates(templateId) { // backward compatibility for releasing the service before releasing the front end @@ -176,34 +310,66 @@ function validateAndFetchTemplates(templateId) { return Promise.resolve(existingProjectTemplate); }) .then((projectTemplate) => { - const phases = _.values(projectTemplate.phases); + // for old projectTemplate with `phases` just get phases config directly from projectTemplate + if (projectTemplate.phases) { + // for now support both ways: creating phases and creating workstreams + const phasesList = _(projectTemplate.phases).omit('workstreamsConfig').values().value(); + const workstreamsConfig = _.get(projectTemplate.phases, 'workstreamsConfig'); + + return { projectTemplate, phasesList, workstreamsConfig }; + } + + // for new projectTemplates try to get phases from the `planConfig`, if it's defined + if (projectTemplate.planConfig) { + return models.PlanConfig.findOneWithLatestRevision(projectTemplate.planConfig).then((planConfig) => { + if (!planConfig) { + const apiErr = new Error(`Cannot find planConfig ${JSON.stringify(projectTemplate.planConfig)}`); + apiErr.status = 400; + throw apiErr; + } + + // for now support both ways: creating phases and creating workstreams + const phasesList = _(planConfig.config).omit('workstreamsConfig').values().value(); + const workstreamsConfig = _.get(planConfig.config, 'workstreamsConfig'); + + return { projectTemplate, phasesList, workstreamsConfig }; + }); + } + + return { projectTemplate }; + }) + .then(({ projectTemplate, phasesList, workstreamsConfig }) => { const productPromises = []; - phases.forEach((phase) => { - // Make sure number of products of per phase <= max value - const productCount = _.isArray(phase.products) ? phase.products.length : 0; - if (productCount > config.maxPhaseProductCount) { - const apiErr = new Error(`Number of products per phase cannot exceed ${config.maxPhaseProductCount}`); - apiErr.status = 400; - throw apiErr; - } - _.map(phase.products, (product) => { - productPromises.push(models.ProductTemplate.findByPk(product.id) - .then((productTemplate) => { - if (!productTemplate) { - // Not found - const apiErr = new Error(`Product template not found for id ${product.id}`); - apiErr.status = 400; - return Promise.reject(apiErr); - } - return Promise.resolve(productTemplate); - })); + if (phasesList) { + phasesList.forEach((phase) => { + // Make sure number of products of per phase <= max value + const productCount = _.isArray(phase.products) ? phase.products.length : 0; + if (productCount > config.maxPhaseProductCount) { + const apiErr = new Error(`Number of products per phase cannot exceed ${config.maxPhaseProductCount}`); + apiErr.status = 400; + throw apiErr; + } + _.map(phase.products, (product) => { + productPromises.push(models.ProductTemplate.findByPk(product.id) + .then((productTemplate) => { + if (!productTemplate) { + // Not found + const apiErr = new Error(`Product template not found for id ${product.id}`); + apiErr.status = 400; + return Promise.reject(apiErr); + } + return Promise.resolve(productTemplate); + })); + }); }); - }); + } if (productPromises.length > 0) { - return Promise.all(productPromises).then(productTemplates => ({ projectTemplate, productTemplates })); + return Promise.all(productPromises).then(productTemplates => ( + { projectTemplate, productTemplates, phasesList, workstreamsConfig } + )); } // if there is no phase or product in a phase is specified, return empty product templates - return Promise.resolve({ projectTemplate, productTemplates: [] }); + return Promise.resolve({ projectTemplate, productTemplates: [], phasesList, workstreamsConfig }); }); } @@ -260,19 +426,30 @@ module.exports = [ let newProject = null; let newPhases; let projectEstimations; + let projectAttachments; models.sequelize.transaction(() => { req.log.debug('Create Project - Starting transaction'); // Validate the templates return validateAndFetchTemplates(project.templateId) // Create project and phases - .then(({ projectTemplate, productTemplates }) => { + .then(({ projectTemplate, productTemplates, phasesList, workstreamsConfig }) => { req.log.debug('Creating project, phase and products'); - return createProjectAndPhases(req, project, projectTemplate, productTemplates); + // only if workstream config is provided, treat such project as using workstreams + // otherwise project would still use phases + if (workstreamsConfig) { + _.set(project, 'details.settings.workstreams', true); + } + return createProjectAndPhases(req, project, projectTemplate, productTemplates, phasesList) + .then(createdProjectAndPhases => + createWorkstreams(req, createdProjectAndPhases.newProject, workstreamsConfig) + .then(() => createdProjectAndPhases), + ); }) .then((createdProjectAndPhases) => { newProject = createdProjectAndPhases.newProject; newPhases = createdProjectAndPhases.newPhases; projectEstimations = createdProjectAndPhases.estimations; + projectAttachments = createdProjectAndPhases.attachments; req.log.debug('new project created (id# %d, name: %s)', newProject.id, newProject.name); // create direct project with name and description @@ -286,21 +463,22 @@ module.exports = [ } req.log.debug('creating project history for project %d', newProject.id); // add to project history asynchronously, don't wait for it to complete - return models.ProjectHistory.create({ + models.ProjectHistory.create({ projectId: newProject.id, status: PROJECT_STATUS.DRAFT, cancelReason: null, updatedBy: req.authUser.userId, }).then(() => req.log.debug('project history created for project %d', newProject.id)) .catch(() => req.log.error('project history failed for project %d', newProject.id)); + return Promise.resolve(); }); }) .then(() => { newProject = newProject.get({ plain: true }); // remove utm details & deletedAt field newProject = _.omit(newProject, ['deletedAt', 'utm']); - // add an empty attachments array - newProject.attachments = []; + // add the project attachments, if any + newProject.attachments = projectAttachments; // set phases array newProject.phases = newPhases; // sets estimations array diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index a8e0a0fd..67bd0169 100644 --- a/src/routes/projects/create.spec.js +++ b/src/routes/projects/create.spec.js @@ -277,31 +277,6 @@ describe('Project create', () => { .expect(400, done); }); - it('should return 201 if error to create direct project', (done) => { - const validBody = _.cloneDeep(body); - validBody.templateId = 3; - const mockHttpClient = _.merge(testUtil.mockHttpClient, { - post: () => Promise.reject(new Error('error message')), - }); - 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) => { - if (err) { - done(err); - } else { - server.services.pubsub.publish.calledWith('project.draft-created').should.be.true; - done(); - } - }); - }); - it('should return 201 if valid user and data', (done) => { const validBody = _.cloneDeep(body); validBody.templateId = 3; diff --git a/src/routes/projects/delete.spec.js b/src/routes/projects/delete.spec.js index 9a9bc3d6..5a4447d3 100644 --- a/src/routes/projects/delete.spec.js +++ b/src/routes/projects/delete.spec.js @@ -19,8 +19,6 @@ const expectAfterDelete = (id, err, next) => { if (!res) { throw new Error('Should found the entity'); } else { - server.services.pubsub.publish.calledWith('project.deleted').should.be.true; - chai.assert.isNotNull(res.deletedAt); chai.assert.isNotNull(res.deletedBy); @@ -29,7 +27,8 @@ const expectAfterDelete = (id, err, next) => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(404, next); + .expect(404) + .end(next); } }), 500); }; @@ -110,7 +109,8 @@ describe('Project delete test', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) - .expect(403, done); + .expect(403) + .end(done); }); it('should return 204 if project was successfully removed', (done) => { diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js index 770f6112..3169a0d6 100644 --- a/src/routes/projects/get.js +++ b/src/routes/projects/get.js @@ -68,6 +68,10 @@ module.exports = [ }) .then((invites) => { project.invites = invites; + return models.ScopeChangeRequest.getProjectScopeChangeRequests(projectId); + }) + .then((scopeChangeRequests) => { + project.scopeChangeRequests = scopeChangeRequests; res.status(200).json(project); }) .catch(err => next(err)); diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js index c6976054..2731d0ab 100644 --- a/src/routes/projects/get.spec.js +++ b/src/routes/projects/get.spec.js @@ -76,7 +76,8 @@ describe('GET Project', () => { it('should return 403 if user is not authenticated', (done) => { request(server) .get(`/v5/projects/${project2.id}`) - .expect(403, done); + .expect(403) + .end(done); }); it('should return 404 if requested project doesn\'t exist', (done) => { @@ -85,16 +86,18 @@ describe('GET Project', () => { .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .expect(404, done); + .expect(404) + .end(done); }); - it('should return 404 if user does not have access to the project', (done) => { + it('should return 403 if user does not have access to the project', (done) => { request(server) .get(`/v5/projects/${project2.id}`) .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) - .expect(403, done); + .expect(403) + .end(done); }); it('should return the project when registerd member attempts to access the project', (done) => { diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index 5c826f13..e06adf8d 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -2,7 +2,7 @@ /* eslint-disable max-len */ import chai from 'chai'; import request from 'supertest'; -import sleep from 'sleep'; +// import sleep from 'sleep'; import config from 'config'; import models from '../../models'; import server from '../../app'; @@ -246,7 +246,7 @@ describe('LIST Project', () => { return Promise.all([esp1, esp2, esp3]); }).then(() => { // sleep for some time, let elasticsearch indices be settled - sleep.sleep(5); + // sleep.sleep(5); done(); }); }); diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index 20beea43..6418bf18 100644 --- a/src/routes/projects/update.js +++ b/src/routes/projects/update.js @@ -56,6 +56,10 @@ const updateProjectValdiations = { bookmarks: Joi.array().items(Joi.object().keys({ title: Joi.string(), address: Joi.string().regex(REGEX.URL), + createdAt: Joi.date(), + createdBy: Joi.number().integer().positive(), + updatedAt: Joi.date(), + updatedBy: Joi.number().integer().positive(), })).optional().allow(null), type: Joi.string().max(45), details: Joi.any(), @@ -79,13 +83,53 @@ const updateProjectValdiations = { }), }; +/** + * Gets scopechange fields either from + * "template.scope" (for old templates) or from "form.scope" (for new templates). + * + * @param {Object} project The project object + * + * @returns {Array} - the scopeChangeFields + */ +const getScopeChangeFields = (project) => { + const scopeChangeFields = _.get(project, 'template.scope.scopeChangeFields'); + const getFromForm = _project => _.get(_project, 'template.form.config.scopeChangeFields'); + + return scopeChangeFields || getFromForm(project); +}; + +const isScopeUpdated = (existingProject, updatedProps) => { + const scopeFields = getScopeChangeFields(existingProject); + + if (scopeFields) { + for (let idx = 0; idx < scopeFields.length; idx += 1) { + const field = scopeFields[idx]; + const oldFieldValue = _.get(existingProject, field); + const updateFieldValue = _.get(updatedProps, field); + if (oldFieldValue !== updateFieldValue) { + return true; + } + } + } + return false; +}; + // NOTE- decided to disable all additional checks for now. const validateUpdates = (existingProject, updatedProps, req) => { const errors = []; switch (existingProject.status) { case PROJECT_STATUS.COMPLETED: - errors.push(`cannot update a project that is in ${existingProject.status}' state`); + errors.push(`cannot update a project that is in '${existingProject.status}' state`); break; + case PROJECT_STATUS.REVIEWED: + case PROJECT_STATUS.ACTIVE: + case PROJECT_STATUS.PAUSED: { + if (isScopeUpdated(existingProject, updatedProps)) { + // TODO commented to disable the scope change flow for immediate release + // errors.push(`Scope changes are not allowed for '${existingProject.status}' project`); + } + break; + } default: break; // disabling this check for now. @@ -150,14 +194,20 @@ module.exports = [ lock: { of: models.Project }, }) .then((_prj) => { - project = _prj; - if (!project) { + if (!_prj) { // handle 404 const err = new Error(`project not found for id ${projectId}`); err.status = 404; return Promise.reject(err); } + if (!_prj.templateId) return Promise.resolve({ _prj }); + return models.ProjectTemplate.getTemplate(_prj.templateId) + .then(template => Promise.resolve({ _prj, template })); + }) + .then(({ _prj, template }) => { + project = _prj; previousValue = _.clone(project.get({ plain: true })); + previousValue.template = template; // run additional validations const validationErrors = validateUpdates(previousValue, updatedProps, req); if (validationErrors.length > 0) { @@ -173,7 +223,9 @@ module.exports = [ const members = req.context.currentProjectMembers; const validRoles = [ PROJECT_MEMBER_ROLE.MANAGER, - PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, ].map(x => x.toLowerCase()); const matchRole = role => _.indexOf(validRoles, role.toLowerCase()) >= 0; if (updatedProps.status === PROJECT_STATUS.ACTIVE && @@ -228,6 +280,7 @@ module.exports = [ ); req.app.emit(EVENT.ROUTING_KEY.PROJECT_UPDATED, { req, + original: previousValue, updated: _.assign({ resource: RESOURCES.PROJECT }, project), }); diff --git a/src/routes/scopeChangeRequests/create.js b/src/routes/scopeChangeRequests/create.js new file mode 100644 index 00000000..e63caae9 --- /dev/null +++ b/src/routes/scopeChangeRequests/create.js @@ -0,0 +1,85 @@ +import _ from 'lodash'; +import Joi from 'joi'; +import validate from 'express-validation'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { SCOPE_CHANGE_REQ_STATUS, PROJECT_MEMBER_ROLE, PROJECT_STATUS } from '../../constants'; +import models from '../../models'; + +/** + * API to add a scope change request for a project. + */ +const permissions = tcMiddleware.permissions; + +const createScopeChangeRequestValidations = { + body: { + oldScope: Joi.object(), + newScope: Joi.object(), + }, +}; + +module.exports = [ + // handles request validations + validate(createScopeChangeRequestValidations), + permissions('project.edit'), + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const oldScope = _.get(req, 'body.oldScope'); + const newScope = _.get(req, 'body.newScope'); + const members = req.context.currentProjectMembers; + const isCustomer = !_.isUndefined(_.find(members, + m => m.userId === req.authUser.userId && m.role === PROJECT_MEMBER_ROLE.CUSTOMER)); + + const scopeChange = { + oldScope, + newScope, + status: isCustomer ? SCOPE_CHANGE_REQ_STATUS.APPROVED : SCOPE_CHANGE_REQ_STATUS.PENDING, + projectId, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }; + + return models.Project.findOne({ + where: { id: projectId }, + }) + + .then((project) => { + if (!project) { + const err = new Error(`Project with id ${projectId} not found`); + err.status = 404; + return Promise.reject(err); + } + + // If the project is not frozen yet, the changes can be saved directly into projects db. + // Scope change request workflow is not required. + const statusesForNonFrozenProjects = [PROJECT_STATUS.DRAFT, PROJECT_STATUS.IN_REVIEW]; + if (statusesForNonFrozenProjects.indexOf(project.status) > -1) { + const err = new Error( + `Cannot create a scope change request for projects with statuses: ${ + statusesForNonFrozenProjects.join(', ')}`); + err.status = 403; + return Promise.reject(err); + } + + return models.ScopeChangeRequest.findPendingScopeChangeRequest(projectId); + }) + + .then((pendingScopeChangeReq) => { + if (pendingScopeChangeReq) { + const err = new Error('Cannot create a new scope change request while there is a pending request'); + err.status = 403; + return Promise.reject(err); + } + + req.log.debug('creating scope change request'); + return models.ScopeChangeRequest.create(scopeChange); + }) + + .then((_newScopeChange) => { + req.log.debug('Created scope change request'); + res.json(_newScopeChange); + return Promise.resolve(); + }) + + .catch(err => next(err)); + }, +]; diff --git a/src/routes/scopeChangeRequests/create.spec.js b/src/routes/scopeChangeRequests/create.spec.js new file mode 100644 index 00000000..ca15f656 --- /dev/null +++ b/src/routes/scopeChangeRequests/create.spec.js @@ -0,0 +1,280 @@ +import sinon from 'sinon'; +import request from 'supertest'; +import _ from 'lodash'; +import Promise from 'bluebird'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +import { PROJECT_STATUS, PROJECT_MEMBER_ROLE, SCOPE_CHANGE_REQ_STATUS } from '../../constants'; + +/** + * Creates a project with given status + * @param {string} status - Status of the project + * + * @returns {Promise} - promise for project creation + */ +function createProject(status) { + const newMember = (userId, role, project) => ({ + userId, + projectId: project.id, + role, + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); + + return models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then(project => + Promise.all([ + models.ProjectMember.create(newMember(testUtil.userIds.member, PROJECT_MEMBER_ROLE.CUSTOMER, project)), + models.ProjectMember.create(newMember(testUtil.userIds.manager, PROJECT_MEMBER_ROLE.MANAGER, project)), + ]).then(() => project), + ); +} + +/** + * creates a new scope change request object + * @returns {Object} - scope change request object + */ +function newScopeChangeRequest() { + return { + newScope: { + appDefinition: { + numberScreens: '5-8', + }, + }, + oldScope: { + appDefinition: { + numberScreens: '2-4', + }, + }, + }; +} + +/** + * Asserts the status of the Scope change request + * @param {Object} response - Response object from the post service + * @param {string} expectedStatus - Expected status of the Scope Change Request + * + * @returns {undefined} - throws error if assertion failed + */ +function assertStatus(response, expectedStatus) { + const status = _.get(response, 'body.status'); + sinon.assert.match(status, expectedStatus); +} + +/** + * Updaes the status of scope change requests for the given project in db + * @param {Object} project - the project + * @param {string} status - the new status for update + * + * @returns {Promise} the promise to update the status + */ +function updateScopeChangeStatuses(project, status) { + return models.ScopeChangeRequest.update({ status }, { where: { projectId: project.id } }); +} + + +describe('Create Scope Change Rquest', () => { + let projects; + let projectWithPendingChange; + let projectWithApprovedChange; + + before((done) => { + const projectStatuses = [ + PROJECT_STATUS.DRAFT, + PROJECT_STATUS.IN_REVIEW, + PROJECT_STATUS.REVIEWED, + PROJECT_STATUS.ACTIVE, + ]; + + Promise.all(projectStatuses.map(status => createProject(status))) + .then(_projects => _projects.map((project, i) => [projectStatuses[i], project])) + .then((_projectStatusPairs) => { + projects = _.fromPairs(_projectStatusPairs); + }) + .then(() => done()); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('POST projects/{projectId}/scopeChangeRequests', () => { + it('Should create scope change request for project in reviewed status', (done) => { + const project = projects[PROJECT_STATUS.REVIEWED]; + + request(server) + .post(`/v5/projects/${project.id}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(newScopeChangeRequest()) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + projectWithPendingChange = project; + + assertStatus(res, SCOPE_CHANGE_REQ_STATUS.PENDING); + done(); + } + }); + }); + + it('Should create scope change request for project in active status', (done) => { + const project = projects[PROJECT_STATUS.ACTIVE]; + + request(server) + .post(`/v5/projects/${project.id}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(newScopeChangeRequest()) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + projectWithApprovedChange = project; + + assertStatus(res, SCOPE_CHANGE_REQ_STATUS.APPROVED); + done(); + } + }); + }); + + it('Should return error with status 403 if project is in draft status', (done) => { + const project = projects[PROJECT_STATUS.DRAFT]; + + request(server) + .post(`/v5/projects/${project.id}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(newScopeChangeRequest()) + .expect(403) + .end(err => done(err)); + }); + + it('Should return error with status 403 if project is in in_review status', (done) => { + const project = projects[PROJECT_STATUS.IN_REVIEW]; + + request(server) + .post(`/v5/projects/${project.id}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(newScopeChangeRequest()) + .expect(403) + .end(err => done(err)); + }); + + it('Should return error with status 404 if project not present', (done) => { + const nonExistentProjectId = 341212; + request(server) + .post(`/v5/projects/${nonExistentProjectId}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(newScopeChangeRequest()) + .expect(404) + .end(err => done(err)); + }); + + it('Should return error with status 403 if there is a request in pending status', (done) => { + request(server) + .post(`/v5/projects/${projectWithPendingChange.id}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(newScopeChangeRequest()) + .expect(403) + .end(err => done(err)); + }); + + it('Should return error with status 403 if there is a request in approved status', (done) => { + request(server) + .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(newScopeChangeRequest()) + .expect(403) + .end(err => done(err)); + }); + + it('Should create scope change request if there is a request in canceled status', (done) => { + updateScopeChangeStatuses(projectWithApprovedChange, SCOPE_CHANGE_REQ_STATUS.CANCELED).then(() => { + request(server) + .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(newScopeChangeRequest()) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + assertStatus(res, SCOPE_CHANGE_REQ_STATUS.APPROVED); + done(); + } + }); + }); + }); + + it('Should create scope change request if there is a request in rejected status', (done) => { + updateScopeChangeStatuses(projectWithApprovedChange, SCOPE_CHANGE_REQ_STATUS.REJECTED).then(() => { + request(server) + .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(newScopeChangeRequest()) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + assertStatus(res, SCOPE_CHANGE_REQ_STATUS.APPROVED); + done(); + } + }); + }); + }); + + it('Should create scope change request if there is a request in activated status', (done) => { + updateScopeChangeStatuses(projectWithApprovedChange, SCOPE_CHANGE_REQ_STATUS.ACTIVATED).then(() => { + request(server) + .post(`/v5/projects/${projectWithApprovedChange.id}/scopeChangeRequests`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(newScopeChangeRequest()) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + assertStatus(res, SCOPE_CHANGE_REQ_STATUS.APPROVED); + done(); + } + }); + }); + }); + }); +}); diff --git a/src/routes/scopeChangeRequests/update.js b/src/routes/scopeChangeRequests/update.js new file mode 100644 index 00000000..1f1e8ea5 --- /dev/null +++ b/src/routes/scopeChangeRequests/update.js @@ -0,0 +1,127 @@ +import _ from 'lodash'; +import Joi from 'joi'; +import validate from 'express-validation'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; +import { + SCOPE_CHANGE_REQ_STATUS, + PROJECT_MEMBER_ROLE, + USER_ROLE, + PROJECT_MEMBER_MANAGER_ROLES, + EVENT, +} from '../../constants'; +import models from '../../models'; + +/** + * API to add a scope change request for a project. + */ +const permissions = tcMiddleware.permissions; + +const updateScopeChangeRequestValidations = { + body: { + status: Joi.string().valid(_.values(SCOPE_CHANGE_REQ_STATUS)), + }, +}; + +/** + * Merges the new scope that's being activated into the details json of the project and updates the db + * @param {Object} req The request object + * @param {Object} newScope The new scope to apply + * @param {string} projectId The project id + * + * @returns {Promise} The promise to update the project with merged data + */ +function updateProjectDetails(req, newScope, projectId) { + return models.Project.findByPk(projectId).then((project) => { + const previousValue = _.clone(project.get({ plain: true })); + + if (!project) { + const err = new Error('Project not found'); + err.status = 404; + return Promise.reject(err); + } + + const updatedDetails = _.mergeWith( + {}, project.details, newScope, + (_objValue, srcValue) => { + if (_.isArray(srcValue)) { + return srcValue; + } + return undefined; + }); + + return project.update({ details: updatedDetails }).then((updatedProject) => { + const updated = updatedProject.get({ plain: true }); + const original = _.omit(previousValue, ['deletedAt', 'deletedBy']); + + // publish original and updated project data + req.app.services.pubsub.publish( + EVENT.ROUTING_KEY.PROJECT_UPDATED, + { original, updated }, + { correlationId: req.id }, + ); + req.app.emit(EVENT.ROUTING_KEY.PROJECT_UPDATED, { req, original, updated }); + + return updatedProject; + }); + }); +} + +module.exports = [ + // handles request validations + validate(updateScopeChangeRequestValidations), + permissions('project.edit'), + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const requestId = _.parseInt(req.params.requestId); + const updatedProps = req.body; + const members = req.context.currentProjectMembers; + const member = _.find(members, m => m.userId === req.authUser.userId); + const isCustomer = member && member.role === PROJECT_MEMBER_ROLE.CUSTOMER; + // const isCopilot = member && member.role === PROJECT_MEMBER_ROLE.COPILOT; + const isManager = member && PROJECT_MEMBER_MANAGER_ROLES.indexOf(member.role) > -1; + const isAdmin = util.hasRoles(req, [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN]); + + req.log.debug('finding scope change', requestId); + return models.ScopeChangeRequest.findScopeChangeRequest(projectId, { requestId }) + .then((scopeChangeReq) => { + // req.log.debug(scopeChangeReq); + if (!scopeChangeReq) { + const err = new Error('Scope change request does not exist'); + err.status = 404; + return next(err); + } + const statusesForCustomers = [SCOPE_CHANGE_REQ_STATUS.APPROVED, SCOPE_CHANGE_REQ_STATUS.REJECTED]; + if (statusesForCustomers.indexOf(updatedProps.status) > -1 && !isCustomer && !isAdmin) { + const err = new Error('Only customer can approve the request'); + err.status = 401; + return next(err); + } + const statusesForManagers = [SCOPE_CHANGE_REQ_STATUS.ACTIVATED]; + if (statusesForManagers.indexOf(updatedProps.status) > -1 && !isManager && !isAdmin) { + const err = new Error('Only managers can activate the request'); + err.status = 401; + return next(err); + } + const statusesForSelf = [SCOPE_CHANGE_REQ_STATUS.CANCELED]; + const isSelf = scopeChangeReq.createdBy === req.authUser.userId; + if (statusesForSelf.indexOf(updatedProps.status) > -1 && !isSelf && !isAdmin) { + const err = new Error('One can cancel only own requests'); + err.status = 401; + return next(err); + } + + return ( + updatedProps.status === SCOPE_CHANGE_REQ_STATUS.ACTIVATED + ? updateProjectDetails(req, scopeChangeReq.newScope, projectId) + : Promise.resolve() + ) + .then(() => scopeChangeReq.update(updatedProps)) + .then((_updatedReq) => { + res.json(_updatedReq); + return Promise.resolve(); + }); + }) + .catch(err => next(err)); + }, +]; diff --git a/src/routes/scopeChangeRequests/update.spec.js b/src/routes/scopeChangeRequests/update.spec.js new file mode 100644 index 00000000..cbceca57 --- /dev/null +++ b/src/routes/scopeChangeRequests/update.spec.js @@ -0,0 +1,213 @@ +import sinon from 'sinon'; +import request from 'supertest'; +import _ from 'lodash'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +import { PROJECT_STATUS, PROJECT_MEMBER_ROLE, SCOPE_CHANGE_REQ_STATUS } from '../../constants'; + +/** + * Creates a project with given status + * @param {string} status - Status of the project + * + * @returns {Promise} - promise for project creation + */ +function createProject(status) { + const newMember = (userId, role, project) => ({ + userId, + projectId: project.id, + role, + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); + + return models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }).then(project => + Promise.all([ + models.ProjectMember.create(newMember(testUtil.userIds.member, PROJECT_MEMBER_ROLE.CUSTOMER, project)), + models.ProjectMember.create(newMember(testUtil.userIds.manager, PROJECT_MEMBER_ROLE.MANAGER, project)), + ]).then(() => project), + ); +} + +/** + * Asserts the status of the Scope change request + * @param {Object} updatedScopeChangeRequest - the updated scope change request from db + * @param {string} expectedStatus - Expected status of the Scope Change Request + * + * @returns {undefined} - throws error if assertion failed + */ +function assertStatus(updatedScopeChangeRequest, expectedStatus) { + sinon.assert.match(updatedScopeChangeRequest.status, expectedStatus); +} + +/** + * create scope change request for the given project + * @param {Object} project - the project + * + * @returns {Promise} - the promise to create scope change request + */ +function createScopeChangeRequest(project) { + return models.ScopeChangeRequest.create({ + newScope: { + appDefinition: { + numberScreens: '5-8', + }, + }, + oldScope: { + appDefinition: { + numberScreens: '2-4', + }, + }, + projectId: project.id, + status: SCOPE_CHANGE_REQ_STATUS.PENDING, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }); +} + +/** + * Updates the details json of the project + * @param {string} projectId The project id + * @param {Object} detailsChange The changes to be merged with details json + * + * @returns {Promise} A promise to update details json in the project + */ +function updateProjectDetails(projectId, detailsChange) { + return models.Project.findByPk(projectId).then((project) => { + const updatedDetails = _.merge({}, project.details, detailsChange); + return project.update({ details: updatedDetails }); + }); +} + +describe('Update Scope Change Rquest', () => { + let project; + let scopeChangeRequest; + + before((done) => { + testUtil + .clearDb() + .then(() => createProject(PROJECT_STATUS.REVIEWED)) + .then((_project) => { + project = _project; + return project; + }) + .then(_project => createScopeChangeRequest(_project)) + .then((_scopeChangeRequest) => { + scopeChangeRequest = _scopeChangeRequest; + return scopeChangeRequest; + }) + .then(() => done()); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('PATCH projects/{projectId}/scopeChangeRequests/{requestId}', () => { + it('Should approve change request with customer login', (done) => { + request(server) + .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + status: SCOPE_CHANGE_REQ_STATUS.APPROVED, + }) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + models.ScopeChangeRequest.findOne({ where: { id: scopeChangeRequest.id } }).then((_scopeChangeRequest) => { + assertStatus(_scopeChangeRequest, SCOPE_CHANGE_REQ_STATUS.APPROVED); + done(); + }); + } + }); + }); + + it('Should activate change request with manager login', (done) => { + // Updating project details before activation. This is used in a later test case + updateProjectDetails(project.id, { apiDefinition: { notes: 'Please include swagger docs' } }).then(() => { + request(server) + .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + status: SCOPE_CHANGE_REQ_STATUS.ACTIVATED, + }) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + models.ScopeChangeRequest.findOne({ where: { id: scopeChangeRequest.id } }) + .then((_scopeChangeRequest) => { + assertStatus(_scopeChangeRequest, SCOPE_CHANGE_REQ_STATUS.ACTIVATED); + done(); + }); + } + }); + }); + }); + + it('Should update details field of project on activation', (done) => { + models.Project.findOne({ where: { id: project.id } }).then((_project) => { + const numberScreens = _.get(_project, 'details.appDefinition.numberScreens'); + sinon.assert.match(numberScreens, '5-8'); + done(); + }); + }); + + it("Should preserve fields of details json that doesn't change the scope on activation", (done) => { + models.Project.findOne({ where: { id: project.id } }).then((_project) => { + const apiNotes = _.get(_project, 'details.apiDefinition.notes'); + sinon.assert.match(apiNotes, 'Please include swagger docs'); + done(); + }); + }); + + it('Should not allow updating oldScope', (done) => { + request(server) + .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + oldScope: {}, + }) + .expect(400) + .end(err => done(err)); + }); + + it('Should not allow updating newScope', (done) => { + request(server) + .patch(`/v5/projects/${project.id}/scopeChangeRequests/${scopeChangeRequest.id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + newScope: {}, + }) + .expect(400) + .end(err => done(err)); + }); + }); +}); diff --git a/src/routes/timelines/list.spec.js b/src/routes/timelines/list.spec.js index 5ebe767f..7df8e58b 100644 --- a/src/routes/timelines/list.spec.js +++ b/src/routes/timelines/list.spec.js @@ -3,7 +3,7 @@ */ import chai from 'chai'; import request from 'supertest'; -import sleep from 'sleep'; +// import sleep from 'sleep'; import config from 'config'; import _ from 'lodash'; @@ -200,7 +200,7 @@ describe('LIST timelines', () => { })) .then(() => { // sleep for some time, let elasticsearch indices be settled - sleep.sleep(5); + // sleep.sleep(5); done(); })); }); diff --git a/src/routes/workItems/create.js b/src/routes/workItems/create.js new file mode 100644 index 00000000..8768596e --- /dev/null +++ b/src/routes/workItems/create.js @@ -0,0 +1,133 @@ +/** + * API to add a work item + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; + +import models from '../../models'; +import { EVENT } from '../../constants'; + +const permissions = require('tc-core-library-js').middleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + workId: Joi.number().integer().positive().required(), + }, + body: { + name: Joi.string().required(), + type: Joi.string().required(), + templateId: Joi.number().positive().optional(), + directProjectId: Joi.number().positive().optional(), + billingAccountId: Joi.number().positive().optional(), + estimatedPrice: Joi.number().positive().optional(), + actualPrice: Joi.number().positive().optional(), + details: Joi.any().optional(), + }, +}; + +module.exports = [ + // validate request payload + validate(schema), + // check permission + permissions('workItem.create'), + // do the real work + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const workStreamId = _.parseInt(req.params.workStreamId); + const phaseId = _.parseInt(req.params.workId); + + const data = req.body; + // default values + _.assign(data, { + projectId, + phaseId, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }); + + let newPhaseProduct = null; + models.sequelize.transaction(() => models.ProjectPhase.findOne({ + where: { + id: phaseId, + projectId, + }, + include: [{ + model: models.WorkStream, + where: { + id: workStreamId, + projectId, + }, + }], + }).then((existing) => { + // make sure work stream exists + if (!existing) { + const err = new Error(`project work stream not found for project id ${projectId}` + + ` and work stream ${workStreamId} and phase id ${phaseId}`); + err.status = 404; + throw err; + } + + return models.Project.findOne({ + where: { id: projectId, deletedAt: { $eq: null } }, + raw: true, + }); + }) + .then((existingProject) => { + // make sure project exists + if (!existingProject) { + const err = new Error(`project not found for project id ${projectId}`); + err.status = 404; + throw err; + } + + _.assign(data, { + phaseId, + projectId, + directProjectId: existingProject.directProjectId, + billingAccountId: existingProject.billingAccountId, + }); + + return models.PhaseProduct.count({ + where: { + projectId, + phaseId, + deletedAt: { $eq: null }, + }, + raw: true, + }); + }) + .then((productCount) => { + // make sure number of products of per phase <= max value + if (productCount >= 100) { + const err = new Error('the number of products per phase cannot exceed ' + + `${100}`); + err.status = 400; + throw err; + } + return models.PhaseProduct.create(data) + .then((_newPhaseProduct) => { + newPhaseProduct = _.cloneDeep(_newPhaseProduct); + req.log.debug('new work created (id# %d, name: %s)', + newPhaseProduct.id, newPhaseProduct.name); + newPhaseProduct = newPhaseProduct.get({ plain: true }); + newPhaseProduct = _.omit(newPhaseProduct, ['deletedAt', 'utm']); + }); + })) + .then(() => { + // Send events to buses + req.log.debug('Sending event to RabbitMQ bus for phase product %d', newPhaseProduct.id); + req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, + newPhaseProduct, + { correlationId: req.id }, + ); + req.log.debug('Sending event to Kafka bus for phase product %d', newPhaseProduct.id); + req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, { req, created: newPhaseProduct }); + + res.status(201).json(newPhaseProduct); + }) + .catch((err) => { next(err); }); + }, +]; diff --git a/src/routes/workItems/create.spec.js b/src/routes/workItems/create.spec.js new file mode 100644 index 00000000..5ad49082 --- /dev/null +++ b/src/routes/workItems/create.spec.js @@ -0,0 +1,340 @@ +/* eslint-disable no-unused-expressions */ +/** + * Tests for create.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; +import sinon from 'sinon'; + +import server from '../../app'; +import models from '../../models'; +import testUtil from '../../tests/util'; +import busApi from '../../services/busApi'; + +import { BUS_API_EVENT } from '../../constants'; + +const should = chai.should(); + +const body = { + name: 'test phase product', + type: 'product1', + estimatedPrice: 20.0, + actualPrice: 1.23456, + details: { + message: 'This can be any json', + }, +}; + +describe('CREATE Work Item', () => { + let projectId; + let workStreamId; + let workId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + models.WorkManagementPermission.create({ + policy: 'workItem.create', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + projectTemplateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then(() => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + models.ProjectPhase.create({ + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + projectId, + }).then((phase) => { + workId = phase.id; + models.PhaseWorkStream.create({ + phaseId: workId, + workStreamId, + }).then(() => { + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]).then(() => done()); + }); + }); + }); + }); + }); + }); + }); + }); + + afterEach((done) => { + testUtil.clearDb(done); + }); + + describe('POST /projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .send(body) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 404 when no work stream with specific workStreamId', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work with specific workId', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 400 when name not provided', (done) => { + const reqBody = _.cloneDeep(body); + delete reqBody.name; + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ param: reqBody }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 when type not provided', (done) => { + const reqBody = _.cloneDeep(body); + delete reqBody.type; + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ param: reqBody }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 when estimatedPrice is negative', (done) => { + const reqBody = _.cloneDeep(body); + reqBody.estimatedPrice = -20; + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ param: reqBody }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 when actualPrice is negative', (done) => { + const reqBody = _.cloneDeep(body); + reqBody.actualPrice = -20; + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ param: reqBody }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 200 for member', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.name.should.be.eql(body.name); + resJson.type.should.be.eql(body.type); + resJson.estimatedPrice.should.be.eql(body.estimatedPrice); + resJson.actualPrice.should.be.eql(body.actualPrice); + resJson.details.should.be.eql(body.details); + done(); + } + }); + }); + + it('should return 201 if payload is valid', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.name.should.be.eql(body.name); + resJson.type.should.be.eql(body.type); + resJson.estimatedPrice.should.be.eql(body.estimatedPrice); + resJson.actualPrice.should.be.eql(body.actualPrice); + resJson.details.should.be.eql(body.details); + done(); + } + }); + }); + + describe('Bus api', () => { + let createEventSpy; + const sandbox = sinon.sandbox.create(); + + 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'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when work item created', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, + sinon.match({ name: body.name })).should.be.false; + done(); + }); + } + }); + }); + }); + }); +}); diff --git a/src/routes/workItems/delete.js b/src/routes/workItems/delete.js new file mode 100644 index 00000000..f8a596e5 --- /dev/null +++ b/src/routes/workItems/delete.js @@ -0,0 +1,92 @@ +/** + * API to delete a work item + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import { EVENT } from '../../constants'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + workId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + // check permission + permissions('workItem.delete'), + + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const workStreamId = _.parseInt(req.params.workStreamId); + const phaseId = _.parseInt(req.params.workId); + const productId = _.parseInt(req.params.id); + + models.sequelize.transaction(() => + models.ProjectPhase.findOne({ + where: { + id: phaseId, + }, + include: [{ + model: models.WorkStream, + where: { + id: workStreamId, + projectId, + }, + }, + ], + }) + .then((existing) => { + if (!existing) { + // handle 404 + const err = new Error('No active work item found for project id ' + + `${projectId}, phase id ${phaseId} and work stream id ${workStreamId}`); + err.status = 404; + return Promise.reject(err); + } + + // soft delete the record + return models.PhaseProduct.findOne({ + where: { + id: productId, + projectId, + phaseId, + deletedAt: { $eq: null }, + }, + }); + }) + .then((existing) => { + if (!existing) { + // handle 404 + const err = new Error('No active work item found for project id ' + + `${projectId}, phase id ${phaseId} and product id ${productId}`); + err.status = 404; + return Promise.reject(err); + } + return existing.update({ deletedBy: req.authUser.userId }); + }) + .then(entity => entity.destroy())) + .then((deleted) => { + req.log.debug('deleted work item', JSON.stringify(deleted, null, 2)); + + // Send events to buses + req.app.services.pubsub.publish( + EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, + deleted, + { correlationId: req.id }, + ); + req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, { req, deleted }); + + res.status(204).json({}); + }) + .catch(err => next(err)); + }, +]; diff --git a/src/routes/workItems/delete.spec.js b/src/routes/workItems/delete.spec.js new file mode 100644 index 00000000..a8aaaa6b --- /dev/null +++ b/src/routes/workItems/delete.spec.js @@ -0,0 +1,287 @@ +/* eslint-disable no-unused-expressions */ +/** + * Tests for delete.js + */ +import _ from 'lodash'; +import request from 'supertest'; +import chai from 'chai'; +import sinon from 'sinon'; + +import server from '../../app'; +import models from '../../models'; +import testUtil from '../../tests/util'; +import busApi from '../../services/busApi'; + +import { BUS_API_EVENT } from '../../constants'; + +chai.should(); + +const expectAfterDelete = (projectId, workStreamId, phaseId, id, err, next) => { + if (err) throw err; + setTimeout(() => + models.PhaseProduct.findOne({ + where: { + id, + projectId, + phaseId, + }, + paranoid: false, + }) + .then((res) => { + if (!res) { + throw new Error('Should found the entity'); + } else { + chai.assert.isNotNull(res.deletedAt); + chai.assert.isNotNull(res.deletedBy); + + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${phaseId}/workitems/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, next); + } + }), 500); +}; +const body = { + name: 'test phase product', + type: 'product1', + estimatedPrice: 20.0, + actualPrice: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, +}; + +describe('DELETE Work Item', () => { + let projectId; + let workStreamId; + let workId; + let productId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + models.WorkManagementPermission.create({ + policy: 'workItem.delete', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + projectTemplateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then(() => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + models.ProjectPhase.create({ + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + projectId, + }).then((phase) => { + workId = phase.id; + models.PhaseWorkStream.create({ + phaseId: workId, + workStreamId, + }) + .then(() => { + _.assign(body, { phaseId: workId, projectId }); + models.PhaseProduct.create(body).then((product) => { + productId = product.id; + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]).then(() => done()); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + afterEach((done) => { + testUtil.clearDb(done); + }); + + describe('DELETE /projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems/{productId}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 404 when no work stream with specific workStreamId', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work with specific workId', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 204 for member', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(204, done); + }); + + it('should return 204 when user have project permission', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(204) + .end(err => expectAfterDelete(projectId, workStreamId, workId, productId, err, done)); + }); + + describe('Bus api', () => { + let createEventSpy; + const sandbox = sinon.sandbox.create(); + + 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'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when work item removed', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(204) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, + sinon.match({ name: body.name })).should.be.false; + done(); + }); + } + }); + }); + }); + }); +}); diff --git a/src/routes/workItems/get.js b/src/routes/workItems/get.js new file mode 100644 index 00000000..44d20716 --- /dev/null +++ b/src/routes/workItems/get.js @@ -0,0 +1,74 @@ +/** + * API to get a work item + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import models from '../../models'; + +const permissions = require('tc-core-library-js').middleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + workId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + // check permission + permissions('workItem.view'), + + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const workStreamId = _.parseInt(req.params.workStreamId); + const phaseId = _.parseInt(req.params.workId); + const productId = _.parseInt(req.params.id); + + models.ProjectPhase.findOne({ + where: { + id: phaseId, + projectId, + }, + include: [{ + model: models.WorkStream, + where: { + id: workStreamId, + projectId, + }, + }, + ], + }) + .then((existing) => { + if (!existing) { + // handle 404 + const err = new Error('No active work item found for project id ' + + `${projectId}, phase id ${phaseId} and work stream id ${workStreamId}`); + err.status = 404; + return Promise.reject(err); + } + + return models.PhaseProduct.findOne({ + where: { + id: productId, + projectId, + phaseId, + deletedAt: { $eq: null }, + }, + }); + }).then((product) => { + if (!product) { + // handle 404 + const err = new Error('phase product not found for project id ' + + `${projectId}, phase id ${phaseId} and product id ${productId}`); + err.status = 404; + throw err; + } else { + res.json(product); + } + }).catch(err => next(err)); + }, +]; diff --git a/src/routes/workItems/get.spec.js b/src/routes/workItems/get.spec.js new file mode 100644 index 00000000..b3acd08c --- /dev/null +++ b/src/routes/workItems/get.spec.js @@ -0,0 +1,234 @@ +/** + * Tests for get.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; +import server from '../../app'; +import models from '../../models'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +const body = { + name: 'test phase product', + type: 'product1', + estimatedPrice: 20.0, + actualPrice: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, +}; + +describe('GET Work Item', () => { + let projectId; + let workStreamId; + let workId; + let productId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]) + .then(() => { + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + models.ProjectPhase.create({ + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + projectId, + }).then((phase) => { + workId = phase.id; + models.PhaseWorkStream.create({ + phaseId: workId, + workStreamId, + }) + .then(() => { + _.assign(body, { phaseId: workId, projectId }); + models.PhaseProduct.create(body).then((product) => { + productId = product.id; + done(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems/{productId}', () => { + it('should return 403 when user have no permission (non team member)', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .expect('Content-Type', /json/) + .expect(403, done); + }); + + it('should return 404 when no project with specific projectId', (done) => { + request(server) + .get(`/v5/projects/9999/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work stream with specific workStreamId', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work with specific workId', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 1 phase when user have project permission (customer)', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .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.name.should.be.eql(body.name); + resJson.type.should.be.eql(body.type); + resJson.estimatedPrice.should.be.eql(body.estimatedPrice); + resJson.actualPrice.should.be.eql(body.actualPrice); + resJson.details.should.be.eql(body.details); + done(); + } + }); + }); + + it('should return 1 phase when user have project permission (copilot)', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .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.name.should.be.eql(body.name); + resJson.type.should.be.eql(body.type); + resJson.estimatedPrice.should.be.eql(body.estimatedPrice); + resJson.actualPrice.should.be.eql(body.actualPrice); + resJson.details.should.be.eql(body.details); + done(); + } + }); + }); + }); +}); diff --git a/src/routes/workItems/list.js b/src/routes/workItems/list.js new file mode 100644 index 00000000..b9dd656d --- /dev/null +++ b/src/routes/workItems/list.js @@ -0,0 +1,63 @@ +/** + * API to get a list of work items + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import models from '../../models'; + +const permissions = require('tc-core-library-js').middleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + workId: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + // validate request payload + validate(schema), + // check permission + permissions('workItem.view'), + + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const workStreamId = _.parseInt(req.params.workStreamId); + const phaseId = _.parseInt(req.params.workId); + + models.ProjectPhase.findOne({ + where: { + id: phaseId, + projectId, + }, + include: [{ + model: models.WorkStream, + where: { + id: workStreamId, + projectId, + }, + }, + ], + }) + .then((existing) => { + if (!existing) { + // handle 404 + const err = new Error('No active phase product found for project id ' + + `${projectId}, work stream id ${workStreamId} and phase id ${phaseId}`); + err.status = 404; + throw err; + } + + return models.PhaseProduct.findAll({ + where: { + phaseId, + projectId, + }, + }); + }) + .then(products => res.json(products)) + .catch(err => next(err)); + }, +]; diff --git a/src/routes/workItems/list.spec.js b/src/routes/workItems/list.spec.js new file mode 100644 index 00000000..bb2cd9fb --- /dev/null +++ b/src/routes/workItems/list.spec.js @@ -0,0 +1,225 @@ +/** + * Tests for list.js + */ +import _ from 'lodash'; +import request from 'supertest'; +import chai from 'chai'; +import server from '../../app'; +import models from '../../models'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +const body = { + name: 'test phase product', + type: 'product1', + estimatedPrice: 20.0, + actualPrice: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, +}; + +describe('LIST Work Items', () => { + let projectId; + let workStreamId; + let workId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]) + .then(() => { + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + models.ProjectPhase.create({ + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + projectId, + }).then((phase) => { + workId = phase.id; + models.PhaseWorkStream.create({ + phaseId: workId, + workStreamId, + }) + .then(() => { + _.assign(body, { phaseId: workId, projectId }); + models.PhaseProduct.create(body).then(() => done()); + }); + }); + }); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems', () => { + it('should return 403 when user have no permission (non team member)', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(403, done); + }); + + it('should return 404 when no project with specific projectId', (done) => { + request(server) + .get(`/v5/projects/9999/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work stream with specific workStreamId', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work with specific workId', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 1 phase when user have project permission (customer)', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .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 return 1 phase when user have project permission (copilot)', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .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/routes/workItems/update.js b/src/routes/workItems/update.js new file mode 100644 index 00000000..96758972 --- /dev/null +++ b/src/routes/workItems/update.js @@ -0,0 +1,117 @@ +/** + * API to update a work item + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; +import { EVENT, RESOURCES } from '../../constants'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + workId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, + body: { + name: Joi.string().optional(), + type: Joi.string().optional(), + templateId: Joi.number().positive().optional(), + directProjectId: Joi.number().positive().optional(), + billingAccountId: Joi.number().positive().optional(), + estimatedPrice: Joi.number().positive().optional(), + actualPrice: Joi.number().positive().optional(), + details: Joi.any().optional(), + }, +}; + + +module.exports = [ + // validate request payload + validate(schema), + // check permission + permissions('workItem.edit'), + + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const workStreamId = _.parseInt(req.params.workStreamId); + const phaseId = _.parseInt(req.params.workId); + const productId = _.parseInt(req.params.id); + + const updatedProps = req.body; + updatedProps.updatedBy = req.authUser.userId; + + let previousValue; + + models.sequelize.transaction(() => models.ProjectPhase.findOne({ + where: { + id: phaseId, + projectId, + }, + include: [{ + model: models.WorkStream, + where: { + id: workStreamId, + projectId, + }, + }, + ], + }) + .then((existingWork) => { + if (!existingWork) { + // handle 404 + const err = new Error('No active work item found for project id ' + + `${projectId}, phase id ${phaseId} and work stream id ${workStreamId}`); + err.status = 404; + return Promise.reject(err); + } + + return models.PhaseProduct.findOne({ + where: { + id: productId, + projectId, + phaseId, + deletedAt: { $eq: null }, + }, + }); + }) + .then((existing) => { + if (!existing) { + // handle 404 + const err = new Error('No active phase product found for project id ' + + `${projectId}, phase id ${phaseId} and product id ${productId}`); + err.status = 404; + throw err; + } + + previousValue = _.clone(existing.get({ plain: true })); + _.extend(existing, updatedProps); + return existing.save().catch(next); + })) + .then((updated) => { + req.log.debug('updated work item', JSON.stringify(updated, null, 2)); + + const updatedValue = updated.get({ plain: true }); + + // emit original and updated project phase information + req.app.services.pubsub.publish( + EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, + { original: previousValue, updated: updatedValue }, + { correlationId: req.id }, + ); + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, + RESOURCES.PHASE_PRODUCT, + _.assign(updatedProps, _.pick(updated, 'id', 'updatedAt')), + ); + + res.json(updated); + }).catch(err => next(err)); + }, +]; diff --git a/src/routes/workItems/update.spec.js b/src/routes/workItems/update.spec.js new file mode 100644 index 00000000..09c0019b --- /dev/null +++ b/src/routes/workItems/update.spec.js @@ -0,0 +1,408 @@ +/* eslint-disable no-unused-expressions */ +/** + * Tests for update.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; +import sinon from 'sinon'; + +import server from '../../app'; +import models from '../../models'; +import testUtil from '../../tests/util'; +import busApi from '../../services/busApi'; +import { BUS_API_EVENT } from '../../constants'; + +const should = chai.should(); + +const body = { + name: 'test phase product', + type: 'product1', + estimatedPrice: 20.0, + actualPrice: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, +}; + +const updateBody = { + name: 'test phase product xxx', + type: 'product2', + estimatedPrice: 123456.789, + actualPrice: 9.8765432, + details: { + message: 'This is another json', + }, +}; + +describe('UPDATE Work Item', () => { + let projectId; + let workStreamId; + let workId; + let productId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + models.WorkManagementPermission.create({ + policy: 'workItem.edit', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + projectTemplateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then(() => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + models.ProjectPhase.create({ + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + projectId, + }).then((phase) => { + workId = phase.id; + models.PhaseWorkStream.create({ + phaseId: workId, + workStreamId, + }) + .then(() => { + _.assign(body, { phaseId: workId, projectId }); + models.PhaseProduct.create(body).then((product) => { + productId = product.id; + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]).then(() => done()); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('PATCH/projects/{projectId}/workstreams/{workStreamId}/works/{workId}/workitems/{productId}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .send(updateBody) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(updateBody) + .expect(403, done); + }); + + it('should return 404 when no work stream with specific workStreamId', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/999/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work with specific workId', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 400 when parameters are invalid', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999/workitems/99999`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + param: { + estimatedPrice: -15, + }, + }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 200 for member', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(updateBody) + .expect(200, done); + }); + + it('should return updated product when user have permission and parameters are valid', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + resJson.name.should.be.eql(updateBody.name); + resJson.type.should.be.eql(updateBody.type); + resJson.estimatedPrice.should.be.eql(updateBody.estimatedPrice); + resJson.actualPrice.should.be.eql(updateBody.actualPrice); + resJson.details.should.be.eql(updateBody.details); + done(); + } + }); + }); + + describe('Bus api', () => { + let createEventSpy; + const sandbox = sinon.sandbox.create(); + + 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'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when name updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + name: 'new name', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ name: 'new name' })).should.be.true; + done(); + }); + } + }); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when estimatedPrice updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + estimatedPrice: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ estimatedPrice: 123 })).should.be.true; + done(); + }); + } + }); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when actualPrice updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + actualPrice: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ actualPrice: 123 })).should.be.true; + done(); + }); + } + }); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when details updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + details: 'something', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ details: 'something' })).should.be.true; + done(); + }); + } + }); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when type updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + type: 'another type', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, + sinon.match({ type: 'another type' })).should.be.true; + done(); + }); + } + }); + }); + }); + }); +}); diff --git a/src/routes/workManagementPermissions/create.js b/src/routes/workManagementPermissions/create.js new file mode 100644 index 00000000..d5be1c7b --- /dev/null +++ b/src/routes/workManagementPermissions/create.js @@ -0,0 +1,59 @@ +/* eslint-disable max-len */ +/** + * API to add a work management permission + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + body: Joi.object().keys({ + policy: Joi.string().max(255).required(), + permission: Joi.object().required(), + projectTemplateId: Joi.number().integer().positive().required(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), +}; + +module.exports = [ + validate(schema), + permissions('workManagementPermission.create'), + (req, res, next) => { + const entity = _.assign(req.body, { + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }); + + // Check if already exists + return models.WorkManagementPermission.findOne({ + where: { + policy: entity.policy, + projectTemplateId: entity.projectTemplateId, + }, + paranoid: false, + }) + .then((existing) => { + if (existing) { + const apiErr = new Error(`Work Management Permission already exists (may be deleted) for policy "${entity.policy}" and project template id ${entity.projectTemplateId}`); + apiErr.status = 400; + return Promise.reject(apiErr); + } + + // Create + return models.WorkManagementPermission.create(entity); + }).then((createdEntity) => { + // Omit deletedAt, deletedBy + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedAt', 'deletedBy')); + }) + .catch(next); + }, +]; diff --git a/src/routes/workManagementPermissions/create.spec.js b/src/routes/workManagementPermissions/create.spec.js new file mode 100644 index 00000000..8c8eb190 --- /dev/null +++ b/src/routes/workManagementPermissions/create.spec.js @@ -0,0 +1,203 @@ +/** + * Tests for create.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import server from '../../app'; +import testUtil from '../../tests/util'; +import models from '../../models'; + +const should = chai.should(); + +describe('CREATE work management permission', () => { + let templateId; + + const body = { + policy: 'work.create', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((t) => { + templateId = t.id; + body.projectTemplateId = templateId; + }).then(() => done()); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('POST /projects/metadata/workManagementPermission', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .send(body) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 403 for manager', (done) => { + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 403 for non-member', (done) => { + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 400 for missing policy', (done) => { + const invalidBody = _.cloneDeep(body); + delete invalidBody.policy; + + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 for missing permission', (done) => { + const invalidBody = _.cloneDeep(body); + delete invalidBody.permission; + + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 for missing projectTemplateId', (done) => { + const invalidBody = _.cloneDeep(body); + delete invalidBody.projectTemplateId; + + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 for duplicated policy and projectTemplateId', (done) => { + models.WorkManagementPermission.create(body) + .then(() => { + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(400, done); + }); + }); + + it('should return 400 for deleted but duplicated policy and projectTemplateId', (done) => { + models.WorkManagementPermission.create(body) + .then((permission) => { + models.WorkManagementPermission.destroy({ where: { id: permission.id } }); + }) + .then(() => { + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(400, done); + }); + }); + + it('should return 201 for admin', (done) => { + request(server) + .post('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + const resJson = res.body; + resJson.policy.should.be.eql(body.policy); + resJson.permission.should.be.eql(body.permission); + resJson.projectTemplateId.should.be.eql(body.projectTemplateId); + resJson.createdBy.should.be.eql(40051333); // admin + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(40051333); // admin + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + }); +}); diff --git a/src/routes/workManagementPermissions/delete.js b/src/routes/workManagementPermissions/delete.js new file mode 100644 index 00000000..1228918a --- /dev/null +++ b/src/routes/workManagementPermissions/delete.js @@ -0,0 +1,37 @@ +/** + * API to delete a work management permission + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('workManagementPermission.delete'), + (req, res, next) => + models.sequelize.transaction(() => + models.WorkManagementPermission.findByPk(req.params.id) + .then((entity) => { + if (!entity) { + const apiErr = new Error(`Work Management Permission not found for id ${req.params.id}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + // Update the deletedBy, then delete + return entity.update({ deletedBy: req.authUser.userId }); + }) + .then(entity => entity.destroy())) + .then(() => { + res.status(204).end(); + }) + .catch(next), +]; diff --git a/src/routes/workManagementPermissions/delete.spec.js b/src/routes/workManagementPermissions/delete.spec.js new file mode 100644 index 00000000..9a24dcff --- /dev/null +++ b/src/routes/workManagementPermissions/delete.spec.js @@ -0,0 +1,211 @@ +/** + * Tests for delete.js + */ +import _ from 'lodash'; +import request from 'supertest'; +import chai from 'chai'; +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const expectAfterDelete = (permissionId, err, next) => { + if (err) throw err; + setTimeout(() => + models.WorkManagementPermission.findOne({ + where: { + id: permissionId, + }, + paranoid: false, + }) + .then((res) => { + if (!res) { + throw new Error('Should found the entity'); + } else { + chai.assert.isNotNull(res.deletedAt); + chai.assert.isNotNull(res.deletedBy); + + request(server) + .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404) + .end(next); + } + }), 500); +}; + +describe('DELETE work management permission', () => { + let permissionId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + + let permission = { + policy: 'work.create', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'permissionId 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['permissionId-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((t) => { + permission = _.assign({}, permission, { projectTemplateId: t.id }); + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: t.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId: project.id, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId: project.id, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]).then(() => { + models.WorkManagementPermission.create(permission) + .then((p) => { + permissionId = p.id; + }) + .then(() => done()); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + + describe('DELETE /projects/metadata/workManagementPermission/{permissionId}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 403 for manager', (done) => { + request(server) + .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(403, done); + }); + + it('should return 404 for non-existed permission', (done) => { + request(server) + .delete('/v5/projects/metadata/workManagementPermission/123') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 for deleted permission', (done) => { + models.WorkManagementPermission.destroy({ where: { id: permissionId } }) + .then(() => { + request(server) + .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + }); + + it('should return 204, for admin, if permission was successfully removed', (done) => { + request(server) + .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end(err => expectAfterDelete(permissionId, err, done)); + }); + + it('should return 204, for connect admin, if permission was successfully removed', (done) => { + request(server) + .delete(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(204) + .end(err => expectAfterDelete(permissionId, err, done)); + }); + }); +}); diff --git a/src/routes/workManagementPermissions/get.js b/src/routes/workManagementPermissions/get.js new file mode 100644 index 00000000..eab1bb1b --- /dev/null +++ b/src/routes/workManagementPermissions/get.js @@ -0,0 +1,38 @@ +/** + * API to get a work management permission + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('workManagementPermission.view'), + (req, res, next) => models.WorkManagementPermission.findOne({ + where: { + id: req.params.id, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((existing) => { + // Not found + if (!existing) { + const apiErr = new Error(`Work Management Permission not found for id ${req.params.id}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + res.json(existing); + return Promise.resolve(); + }) + .catch(next), +]; diff --git a/src/routes/workManagementPermissions/get.spec.js b/src/routes/workManagementPermissions/get.spec.js new file mode 100644 index 00000000..29457681 --- /dev/null +++ b/src/routes/workManagementPermissions/get.spec.js @@ -0,0 +1,149 @@ +/** + * Tests for get.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('GET work management permission', () => { + let permissionId; + + let permission = { + policy: 'work.create', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((t) => { + permission = _.assign({}, permission, { projectTemplateId: t.id }); + models.WorkManagementPermission.create(permission) + .then((p) => { + permissionId = p.id; + }) + .then(() => done()); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/metadata/workManagementPermission/{permissionId}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 403 for manager', (done) => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(403, done); + }); + + it('should return 403 for non-member', (done) => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .expect(403, done); + }); + + it('should return 404 for non-existed permission', (done) => { + request(server) + .get('/v5/projects/metadata/workManagementPermission/1234') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 for deleted permission', (done) => { + models.WorkManagementPermission.destroy({ where: { id: permissionId } }) + .then(() => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + }); + + it('should return 200 for admin', (done) => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.id.should.be.eql(permissionId); + resJson.policy.should.be.eql(permission.policy); + resJson.permission.should.be.eql(permission.permission); + resJson.projectTemplateId.should.be.eql(permission.projectTemplateId); + resJson.createdBy.should.be.eql(permission.createdBy); + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(permission.updatedBy); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + }); +}); diff --git a/src/routes/workManagementPermissions/list.js b/src/routes/workManagementPermissions/list.js new file mode 100644 index 00000000..cef27bd8 --- /dev/null +++ b/src/routes/workManagementPermissions/list.js @@ -0,0 +1,43 @@ +/** + * API to list all work management permissions + */ +import validate from 'express-validation'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import Joi from 'joi'; +import util from '../../util'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + query: { + filter: Joi.string().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('workManagementPermission.view'), + (req, res, next) => { + // handle filters + const filters = util.parseQueryFilter(req.query.filter); + // Throw error if projectTemplateId is not present in filter + if (!filters.projectTemplateId) { + return next(util.buildApiError('Missing filter projectTemplateId', 400)); + } + if (!util.isValidFilter(filters, ['projectTemplateId'])) { + return util.handleError('Invalid filters', null, req, next); + } + req.log.debug(filters); + + return models.WorkManagementPermission.findAll({ + where: filters, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((result) => { + res.json(result); + }) + .catch(next); + }, +]; diff --git a/src/routes/workManagementPermissions/list.spec.js b/src/routes/workManagementPermissions/list.spec.js new file mode 100644 index 00000000..0a9728ad --- /dev/null +++ b/src/routes/workManagementPermissions/list.spec.js @@ -0,0 +1,241 @@ +/* eslint-disable max-len */ +/** + * Tests for list.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('LIST work management permissions', () => { + let templateIds; + + const templates = [ + { + name: 'template 1', + key: 'key 1', + category: 'category 1', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + disabled: true, + hidden: true, + scope: { + scope1: { + subScope1A: 1, + subScope1B: 2, + }, + scope2: [1, 2, 3], + }, + phases: { + phase1: { + name: 'phase 1', + details: { + anyDetails: 'any details 1', + }, + others: ['others 11', 'others 12'], + }, + phase2: { + name: 'phase 2', + details: { + anyDetails: 'any details 2', + }, + others: ['others 21', 'others 22'], + }, + }, + createdBy: 1, + updatedBy: 1, + }, + { + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }, + ]; + const permissions = [ + { + policy: 'work.create', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }, + { + policy: 'work.edit', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }, + ]; + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.bulkCreate(templates, { returning: true }) + .then((t) => { + templateIds = _.map(t, template => template.id); + const newPermissions = _.map(permissions, p => _.assign({}, p, { projectTemplateId: templateIds[0] })); + newPermissions.push(_.assign({}, permissions[0], { projectTemplateId: templateIds[1] })); + models.WorkManagementPermission.bulkCreate(newPermissions, { returning: true }) + .then(() => done()); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/metadata/workManagementPermission', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1') + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1') + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1') + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 403 for manager', (done) => { + request(server) + .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1') + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(403, done); + }); + + it('should return 403 for non-member', (done) => { + request(server) + .get('/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1') + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .expect(403, done); + }); + + it('should return 400 for missing filter', (done) => { + request(server) + .get('/v5/projects/metadata/workManagementPermission') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 for missing projectTemplateId', (done) => { + request(server) + .get('/v5/projects/metadata/workManagementPermission?filter=template') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 for invalid filter', (done) => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission?filter=invalid%3D2%26projectTemplateId%3D${templateIds[0]}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(500, done); + }); + + + it('should return 200 for admin for projectTemplateId=1', (done) => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D${templateIds[0]}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.have.length(2); + resJson[0].policy.should.be.eql(permissions[0].policy); + resJson[0].permission.should.be.eql(permissions[0].permission); + resJson[0].projectTemplateId.should.be.eql(templateIds[0]); + should.exist(resJson[0].createdAt); + resJson[0].updatedBy.should.be.eql(permissions[0].updatedBy); + should.exist(resJson[0].updatedAt); + should.not.exist(resJson[0].deletedBy); + should.not.exist(resJson[0].deletedAt); + resJson[1].policy.should.be.eql(permissions[1].policy); + resJson[1].permission.should.be.eql(permissions[1].permission); + resJson[1].projectTemplateId.should.be.eql(templateIds[0]); + should.exist(resJson[1].createdAt); + resJson[1].updatedBy.should.be.eql(permissions[1].updatedBy); + should.exist(resJson[1].updatedAt); + should.not.exist(resJson[1].deletedBy); + should.not.exist(resJson[1].deletedAt); + + done(); + }); + }); + + it('should return 200 for admin for projectTemplateId=2', (done) => { + request(server) + .get(`/v5/projects/metadata/workManagementPermission?filter=projectTemplateId%3D${templateIds[1]}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.have.length(1); + resJson[0].policy.should.be.eql(permissions[0].policy); + resJson[0].permission.should.be.eql(permissions[0].permission); + resJson[0].projectTemplateId.should.be.eql(templateIds[1]); + should.exist(resJson[0].createdAt); + resJson[0].updatedBy.should.be.eql(permissions[0].updatedBy); + should.exist(resJson[0].updatedAt); + should.not.exist(resJson[0].deletedBy); + should.not.exist(resJson[0].deletedAt); + + done(); + }); + }); + }); +}); diff --git a/src/routes/workManagementPermissions/update.js b/src/routes/workManagementPermissions/update.js new file mode 100644 index 00000000..7e18851b --- /dev/null +++ b/src/routes/workManagementPermissions/update.js @@ -0,0 +1,81 @@ +/* eslint-disable max-len */ +/** + * API to update a work management permission + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + id: Joi.number().integer().positive().required(), + }, + body: Joi.object().keys({ + policy: Joi.string().max(255).optional(), + permission: Joi.object().optional(), + projectTemplateId: Joi.number().integer().positive().optional(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), +}; + +module.exports = [ + validate(schema), + permissions('workManagementPermission.edit'), + (req, res, next) => { + const entityToUpdate = _.assign(req.body, { + updatedBy: req.authUser.userId, + }); + + let permissionToUpdate; + + return models.sequelize.transaction(() => // Get work management permission + models.WorkManagementPermission.findOne({ + where: { + id: req.params.id, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((permission) => { + // Not found + if (!permission) { + const apiErr = new Error(`Work Management Permission not found for id ${req.params.id}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + permissionToUpdate = permission; + return models.WorkManagementPermission.findOne({ + where: { + policy: entityToUpdate.policy, + projectTemplateId: entityToUpdate.projectTemplateId, + id: { $ne: req.params.id }, + }, + paranoid: false, + }); + }) + .then((existing) => { + if (existing) { + const apiErr = new Error(`Work Management Permission already exists (may be deleted) for policy "${entityToUpdate.policy}" and project template id ${entityToUpdate.projectTemplateId}`); + apiErr.status = 400; + return Promise.reject(apiErr); + } + + return permissionToUpdate.update(entityToUpdate); + }), + ) + .then((updated) => { + res.json(updated); + return Promise.resolve(); + }) + .catch(next); + }, +]; diff --git a/src/routes/workManagementPermissions/update.spec.js b/src/routes/workManagementPermissions/update.spec.js new file mode 100644 index 00000000..cb162481 --- /dev/null +++ b/src/routes/workManagementPermissions/update.spec.js @@ -0,0 +1,248 @@ +/** + * Tests for update.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('UPDATE work management permission', () => { + let permissionId; + let templateId; + + let permission = { + policy: 'work.create', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((t) => { + templateId = t.id; + permission = _.assign({}, permission, { projectTemplateId: templateId }); + models.WorkManagementPermission.create(permission) + .then((p) => { + permissionId = p.id; + }) + .then(() => done()); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('PATCH /projects/metadata/workManagementPermission/{permissionId}', () => { + const body = { + policy: 'work.edit', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + createdBy: 1, + updatedBy: 1, + }; + + it('should return 403 if user is not authenticated', (done) => { + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .send(body) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .send(body) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 403 for manager', (done) => { + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .send(body) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(403, done); + }); + + it('should return 403 for non-member', (done) => { + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member2}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 404 for non-existed type', (done) => { + request(server) + .patch('/v5/projects/metadata/workManagementPermission/1234') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(404) + .end(done); + }); + + it('should return 404 for deleted type', (done) => { + models.WorkManagementPermission.destroy({ where: { id: permissionId } }) + .then(() => { + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(404) + .end(done); + }); + }); + + it('should return 400 when updated with invalid param', (done) => { + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ invalid: null }) + .expect('Content-Type', /json/) + .expect(400) + .end(done); + }); + + it('should return 400 for policy and projectTemplateId updated with existing(non-deleted) values', (done) => { + const newParam = _.assign({}, body, { projectTemplateId: templateId }); + models.WorkManagementPermission.create(newParam) + .then(() => { + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newParam) + .expect('Content-Type', /json/) + .expect(400) + .end(done); + }); + }); + + it('should return 400 for policy and projectTemplateId updated with existing(deleted) values', (done) => { + const newParam = _.assign({}, body, { projectTemplateId: templateId }); + models.WorkManagementPermission.create(newParam) + .then((p) => { + models.WorkManagementPermission.destroy({ where: { id: p.id } }); + }) + .then(() => { + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newParam) + .expect('Content-Type', /json/) + .expect(400) + .end(done); + }); + }); + + it('should return 200 for permission updated', (done) => { + const partialBody = _.assign({}, body, { projectTemplateId: templateId }); + delete partialBody.permission; + + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(partialBody) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.id.should.be.eql(permissionId); + resJson.policy.should.be.eql(partialBody.policy); + resJson.permission.should.be.eql(permission.permission); + resJson.projectTemplateId.should.be.eql(permission.projectTemplateId); + resJson.createdBy.should.be.eql(permission.createdBy); // should not update createdAt + resJson.updatedBy.should.be.eql(40051333); // admin + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + + it('should return 200 for admin all fields updated', (done) => { + const newParam = _.assign({}, body, { projectTemplateId: templateId }); + request(server) + .patch(`/v5/projects/metadata/workManagementPermission/${permissionId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(newParam) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.id.should.be.eql(permissionId); + resJson.policy.should.be.eql(body.policy); + resJson.permission.should.be.eql(body.permission); + resJson.projectTemplateId.should.be.eql(newParam.projectTemplateId); + resJson.createdBy.should.be.eql(permission.createdBy); // should not update createdAt + resJson.updatedBy.should.be.eql(40051333); // admin + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + }); +}); diff --git a/src/routes/workStreams/create.js b/src/routes/workStreams/create.js new file mode 100644 index 00000000..bcd72213 --- /dev/null +++ b/src/routes/workStreams/create.js @@ -0,0 +1,70 @@ +/** + * API to add a work stream + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; +import models from '../../models'; +import { WORKSTREAM_STATUS } from '../../constants'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + }, + body: Joi.object().keys({ + name: Joi.string().max(255).required(), + type: Joi.string().max(45).required(), + status: Joi.string().valid(_.values(WORKSTREAM_STATUS)).required(), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), +}; + +module.exports = [ + validate(schema), + permissions('workStream.create'), + // do the real work + (req, res, next) => { + const data = req.body; + // default values + const projectId = _.parseInt(req.params.projectId); + _.assign(data, { + projectId, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }); + + models.sequelize.transaction(() => { + req.log.debug('Create WorkStream - Starting transaction'); + return models.Project.findOne({ + where: { id: projectId, deletedAt: { $eq: null } }, + }) + .then((existingProject) => { + if (!existingProject) { + const err = new Error(`active project not found for project id ${projectId}`); + err.status = 404; + throw err; + } + + return models.WorkStream.create(data); + }) + .catch(next); + }) + .then((createdEntity) => { + req.log.debug('new work stream created (id# %d, name: %s)', + createdEntity.id, createdEntity.name); + res.status(201).json(_.omit(createdEntity.toJSON(), 'deletedBy', 'deletedAt')); + }) + .catch((err) => { + util.handleError('Error creating work stream', err, req, next); + }); + }, +]; diff --git a/src/routes/workStreams/create.spec.js b/src/routes/workStreams/create.spec.js new file mode 100644 index 00000000..4a66cdb8 --- /dev/null +++ b/src/routes/workStreams/create.spec.js @@ -0,0 +1,255 @@ +/** + * Tests for create.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import server from '../../app'; +import testUtil from '../../tests/util'; +import models from '../../models'; + +const should = chai.should(); + +describe('CREATE work stream', () => { + const templates = [ + { + name: 'template 1', + key: 'key 1', + category: 'category 1', + icon: 'http://example.com/icon1.ico', + question: 'question 1', + info: 'info 1', + aliases: ['key-1', 'key_1'], + disabled: true, + hidden: true, + scope: { + scope1: { + subScope1A: 1, + subScope1B: 2, + }, + scope2: [1, 2, 3], + }, + phases: { + phase1: { + name: 'phase 1', + details: { + anyDetails: 'any details 1', + }, + others: ['others 11', 'others 12'], + }, + phase2: { + name: 'phase 2', + details: { + anyDetails: 'any details 2', + }, + others: ['others 21', 'others 22'], + }, + }, + createdBy: 1, + updatedBy: 1, + }, + { + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }, + ]; + + let projectId; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.bulkCreate(templates, { returning: true }) + .then((t) => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: t[0].id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + }) + .then(() => done()); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('POST /projects/{projectId}/workstreams', () => { + const body = { + name: 'Work Stream', + type: 'generic', + status: 'active', + }; + + it('should return 403 if user is not authenticated', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams`) + .send(body) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 404 for non-existed project id', (done) => { + request(server) + .delete('/v5/projects/999/workstreams') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 for deleted type', (done) => { + models.Project.destroy({ where: { id: projectId } }) + .then(() => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 403 for manager', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 400 for missing type', (done) => { + const invalidBody = _.cloneDeep(body); + delete invalidBody.type; + + request(server) + .post(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 for missing name', (done) => { + const invalidBody = _.cloneDeep(body); + delete invalidBody.name; + + request(server) + .post(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 for status', (done) => { + const invalidBody = _.cloneDeep(body); + delete invalidBody.status; + + request(server) + .post(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 201 for admin', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + const resJson = res.body; + resJson.name.should.be.eql(body.name); + resJson.type.should.be.eql(body.type); + resJson.status.should.be.eql(body.status); + resJson.projectId.should.be.eql(projectId); + + resJson.createdBy.should.be.eql(40051333); + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(40051333); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + + it('should return 201 for connect admin', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err, res) => { + const resJson = res.body; + resJson.name.should.be.eql(body.name); + resJson.type.should.be.eql(body.type); + resJson.status.should.be.eql(body.status); + resJson.projectId.should.be.eql(projectId); + resJson.createdBy.should.be.eql(40051336); + resJson.updatedBy.should.be.eql(40051336); + done(); + }); + }); + }); +}); diff --git a/src/routes/workStreams/delete.js b/src/routes/workStreams/delete.js new file mode 100644 index 00000000..aa2ef463 --- /dev/null +++ b/src/routes/workStreams/delete.js @@ -0,0 +1,44 @@ +/** + * API to delete a work stream + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('workStream.delete'), + (req, res, next) => + models.sequelize.transaction(() => + models.WorkStream.findOne({ + where: { + id: req.params.id, + projectId: req.params.projectId, + }, + }) + .then((entity) => { + if (!entity) { + const apiErr = new Error(`Work Stream not found for id ${req.params.id} ` + + `and project id ${req.params.projectId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + // Update the deletedBy, then delete + return entity.update({ deletedBy: req.authUser.userId }); + }) + .then(entity => entity.destroy())) + .then(() => { + res.status(204).end(); + }) + .catch(next), +]; diff --git a/src/routes/workStreams/delete.spec.js b/src/routes/workStreams/delete.spec.js new file mode 100644 index 00000000..5a3b92b6 --- /dev/null +++ b/src/routes/workStreams/delete.spec.js @@ -0,0 +1,168 @@ +/** + * Tests for delete.js + */ +import request from 'supertest'; +import chai from 'chai'; +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const expectAfterDelete = (id, projectId, err, next) => { + if (err) throw err; + setTimeout(() => + models.WorkStream.findOne({ + where: { + id, + }, + paranoid: false, + }) + .then((res) => { + if (!res) { + throw new Error('Should found the entity'); + } else { + chai.assert.isNotNull(res.deletedAt); + chai.assert.isNotNull(res.deletedBy); + + request(server) + .get(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, next); + } + }), 500); +}; + +describe('DELETE work stream', () => { + let projectId; + let id; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + id = entity.id; + done(); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('DELETE /projects/{projectId}/workstreams/{id}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${id}`) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 403 for manager', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(403, done); + }); + + it('should return 404 for non-existed type', (done) => { + request(server) + .delete('/v5/projects/metadata/projectTypes/not_existed') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 for deleted type', (done) => { + models.WorkStream.destroy({ where: { id } }) + .then(() => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + }); + + it('should return 204, for admin, if type was successfully removed', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end(err => expectAfterDelete(id, projectId, err, done)); + }); + + it('should return 204, for connect admin, if type was successfully removed', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(204) + .end(err => expectAfterDelete(id, projectId, err, done)); + }); + }); +}); diff --git a/src/routes/workStreams/get.js b/src/routes/workStreams/get.js new file mode 100644 index 00000000..83ac929f --- /dev/null +++ b/src/routes/workStreams/get.js @@ -0,0 +1,41 @@ +/** + * API to get a work stream + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('workStream.view'), + (req, res, next) => models.WorkStream.findOne({ + where: { + id: req.params.id, + projectId: req.params.projectId, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((workStream) => { + // Not found + if (!workStream) { + const apiErr = new Error(`work stream not found for project id ${req.params.projectId} ` + + `and work stream id ${req.params.id}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + res.json(workStream); + return Promise.resolve(); + }) + .catch(next), +]; diff --git a/src/routes/workStreams/get.spec.js b/src/routes/workStreams/get.spec.js new file mode 100644 index 00000000..d696da90 --- /dev/null +++ b/src/routes/workStreams/get.spec.js @@ -0,0 +1,162 @@ +/** + * Tests for get.js + */ +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('GET work stream', () => { + let projectId; + let id; + let workStream; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + id = entity.id; + workStream = entity; + done(); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/workstreams/{id}', () => { + it('should return 404 for non-existed work stream', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/1234`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + + it('should return 404 for deleted work stream', (done) => { + models.WorkStream.destroy({ where: { id } }) + .then(() => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + }); + + it('should return 200 for admin', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.name.should.be.eql(workStream.name); + resJson.type.should.be.eql(workStream.type); + resJson.status.should.be.eql(workStream.status); + resJson.projectId.should.be.eql(workStream.projectId); + resJson.createdBy.should.be.eql(workStream.createdBy); + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(workStream.updatedBy); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${id}`) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 200 for connect admin', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(200) + .end(done); + }); + + it('should return 200 for connect manager', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(200) + .end(done); + }); + }); +}); diff --git a/src/routes/workStreams/list.js b/src/routes/workStreams/list.js new file mode 100644 index 00000000..c536e2b9 --- /dev/null +++ b/src/routes/workStreams/list.js @@ -0,0 +1,45 @@ +/** + * API to list all work streams + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('workStream.view'), + (req, res, next) => { + const projectId = req.params.projectId; + models.Project.count({ + where: { + id: projectId, + }, + }) + .then((countProject) => { + if (countProject === 0) { + const apiErr = new Error(`active project not found for project id ${projectId}`); + apiErr.status = 404; + throw apiErr; + } + + return models.WorkStream.findAll({ + where: { + projectId, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }); + }) + .then(workStreams => res.json(workStreams)) + .catch(next); + }, +]; diff --git a/src/routes/workStreams/list.spec.js b/src/routes/workStreams/list.spec.js new file mode 100644 index 00000000..371ccc95 --- /dev/null +++ b/src/routes/workStreams/list.spec.js @@ -0,0 +1,157 @@ +/** + * Tests for list.js + */ +import chai from 'chai'; +import _ from 'lodash'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('LIST work streams', () => { + const workStreams = [{ + name: 'Work Stream 1', + type: 'generic', + status: 'active', + createdBy: 1, + updatedBy: 1, + }, { + name: 'Work Stream 2', + type: 'generic', + status: 'reviewed', + createdBy: 1, + updatedBy: 1, + }]; + + let projectId; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.WorkStream.bulkCreate(_.map(workStreams, w => _.assign(w, { projectId }))).then(() => done()); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/workstreams', () => { + it('should return 200 for admin', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const workStream = workStreams[0]; + + const resJson = res.body; + resJson.should.have.length(2); + resJson[0].name.should.be.eql(workStream.name); + resJson[0].type.should.be.eql(workStream.type); + resJson[0].status.should.be.eql(workStream.status); + resJson[0].projectId.should.be.eql(workStream.projectId); + should.exist(resJson[0].createdAt); + resJson[0].updatedBy.should.be.eql(workStream.updatedBy); + should.exist(resJson[0].updatedAt); + should.not.exist(resJson[0].deletedBy); + should.not.exist(resJson[0].deletedAt); + + done(); + }); + }); + + it('should return 404 for deleted project', (done) => { + models.Project.destroy({ where: { id: projectId } }) + .then(() => { + request(server) + .get(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + }); + + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams`) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 200 for connect admin', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(200) + .end(done); + }); + + it('should return 200 for connect manager', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(200) + .end(done); + }); + }); +}); diff --git a/src/routes/workStreams/update.js b/src/routes/workStreams/update.js new file mode 100644 index 00000000..e370c80a --- /dev/null +++ b/src/routes/workStreams/update.js @@ -0,0 +1,65 @@ +/** + * API to update a work stream + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import { WORKSTREAM_STATUS } from '../../constants'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, + body: Joi.object().keys({ + id: Joi.number().valid(Joi.ref('$params.id')), + name: Joi.string().max(255), + type: Joi.string().max(45), + status: Joi.string().valid(_.values(WORKSTREAM_STATUS)), + createdAt: Joi.any().strip(), + updatedAt: Joi.any().strip(), + deletedAt: Joi.any().strip(), + createdBy: Joi.any().strip(), + updatedBy: Joi.any().strip(), + deletedBy: Joi.any().strip(), + }).required(), +}; + +module.exports = [ + validate(schema), + permissions('workStream.edit'), + (req, res, next) => { + const entityToUpdate = _.assign(req.body, { + updatedBy: req.authUser.userId, + }); + const projectId = req.params.projectId; + const workStreamId = req.params.id; + + return models.WorkStream.findOne({ + where: { + id: workStreamId, + projectId, + }, + }) + .then((workStream) => { + if (!workStream) { + // handle 404 + const err = new Error(`work stream not found for project id ${projectId} ` + + `and work stream id ${workStreamId}`); + err.status = 404; + return Promise.reject(err); + } + + return workStream.update(entityToUpdate); + }) + .then((workStream) => { + res.json(workStream); + return Promise.resolve(); + }) + .catch(next); + }, +]; diff --git a/src/routes/workStreams/update.spec.js b/src/routes/workStreams/update.spec.js new file mode 100644 index 00000000..96c074f5 --- /dev/null +++ b/src/routes/workStreams/update.spec.js @@ -0,0 +1,230 @@ +/** + * Tests for update.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('UPDATE Work Stream', () => { + let projectId; + let id; + let workStream; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + models.WorkManagementPermission.create({ + policy: 'workStream.edit', + permission: { + allowRule: { projectRoles: ['manager', 'copilot'], topcoderRoles: ['Connect Admin', 'administrator'] }, + denyRule: { projectRoles: ['copilot'] }, + }, + projectTemplateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then(() => { + // Create project + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + id = entity.id; + workStream = entity; + done(); + }); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('PATCH /projects/{projectId}/workstreams/{id}', () => { + const body = { + name: 'Work Stream', + type: 'generic', + status: 'active', + }; + + it('should return 403 if user is not authenticated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${id}`) + .send(body) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${id}`) + .send(body) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 403 for manager', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${id}`) + .send(body) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(403, done); + }); + + it('should return 404 for non-existed work stream', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/1234`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(404, done); + }); + + it('should return 404 for deleted work stream', (done) => { + models.WorkStream.destroy({ where: { id } }) + .then(() => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(404, done); + }); + }); + + it('should return 200 for admin name updated', (done) => { + const partialBody = _.cloneDeep(body); + delete partialBody.type; + delete partialBody.status; + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(partialBody) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.id.should.be.eql(id); + resJson.name.should.be.eql(workStream.name); + resJson.type.should.be.eql(workStream.type); + resJson.status.should.be.eql(workStream.status); + resJson.projectId.should.be.eql(workStream.projectId); + resJson.createdBy.should.be.eql(workStream.createdBy); + resJson.createdBy.should.be.eql(workStream.createdBy); + resJson.updatedBy.should.be.eql(40051333); // admin + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + + it('should return 200 for admin all fields updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.id.should.be.eql(id); + resJson.name.should.be.eql(workStream.name); + resJson.type.should.be.eql(workStream.type); + resJson.status.should.be.eql(workStream.status); + resJson.projectId.should.be.eql(workStream.projectId); + resJson.createdBy.should.be.eql(workStream.createdBy); + resJson.updatedBy.should.be.eql(40051333); // admin + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + + it('should return 200 for connect admin', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .send(body) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.id.should.be.eql(id); + resJson.name.should.be.eql(workStream.name); + resJson.type.should.be.eql(workStream.type); + resJson.status.should.be.eql(workStream.status); + resJson.projectId.should.be.eql(workStream.projectId); + resJson.createdBy.should.be.eql(workStream.createdBy); + resJson.updatedBy.should.be.eql(40051336); // connect admin + done(); + }); + }); + }); +}); diff --git a/src/routes/works/create.js b/src/routes/works/create.js new file mode 100644 index 00000000..a1d3cf5e --- /dev/null +++ b/src/routes/works/create.js @@ -0,0 +1,159 @@ +/** + * API to add a phase as work + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import Sequelize from 'sequelize'; + +import models from '../../models'; +import util from '../../util'; +import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants'; + +const permissions = require('tc-core-library-js').middleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + }, + body: Joi.object().keys({ + name: Joi.string().required(), + description: Joi.string().optional(), + requirements: Joi.string().optional(), + status: Joi.string().required(), + startDate: Joi.date().optional(), + endDate: Joi.date().optional(), + duration: Joi.number().min(0).optional(), + budget: Joi.number().min(0).optional(), + spentBudget: Joi.number().min(0).optional(), + progress: Joi.number().min(0).optional(), + details: Joi.any().optional(), + order: Joi.number().integer().optional(), + productTemplateId: Joi.number().integer().positive().optional(), + }).required(), +}; + +module.exports = [ + // validate request payload + validate(schema), + // check permission + permissions('work.create'), + // do the real work + (req, res, next) => { + // default values + const projectId = _.parseInt(req.params.projectId); + const workStreamId = _.parseInt(req.params.workStreamId); + + const data = req.body; + _.assign(data, { + projectId, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }); + + let existingWorkStream = null; + let newProjectPhase = null; + + req.log.debug('Create Work - Starting transaction'); + return models.sequelize.transaction(() => + models.WorkStream.findOne({ + where: { + id: workStreamId, + projectId, + deletedAt: { $eq: null }, + }, + }) + .then((_existingWorkStream) => { + if (!_existingWorkStream) { + // handle 404 + const err = new Error(`active work stream not found for project id ${projectId} ` + + `and work stream id ${workStreamId}`); + err.status = 404; + throw err; + } + + existingWorkStream = _existingWorkStream; + + if (data.startDate !== null && data.endDate !== null && data.startDate > data.endDate) { + const err = new Error('startDate must not be after endDate.'); + err.status = 400; + throw err; + } + return models.ProjectPhase.create(data); + }) + .then((_newProjectPhase) => { + newProjectPhase = _.omit(_newProjectPhase.toJSON(), ['deletedAt', 'deletedBy']); + return existingWorkStream.addProjectPhase(_newProjectPhase.id); + }) + .then(() => { + req.log.debug('re-ordering the other phases'); + + if (_.isNil(newProjectPhase.order)) { + return Promise.resolve(); + } + // Increase the order of the other phases in the same project, + // which have `order` >= this phase order + return models.ProjectPhase.update({ order: Sequelize.literal('"order" + 1') }, { + where: { + projectId, + id: { $ne: newProjectPhase.id }, + order: { $gte: newProjectPhase.order }, + }, + }); + }) + .then(() => { + if (_.isNil(data.productTemplateId)) { + return Promise.resolve(); + } + + // Get the product template + return models.ProductTemplate.findByPk(data.productTemplateId) + .then((productTemplate) => { + if (!productTemplate) { + const err = new Error(`Product template does not exist with id = ${data.productTemplateId}`); + err.status = 400; + throw err; + } + + // Create the phase product + return models.PhaseProduct.create({ + name: productTemplate.name, + templateId: data.productTemplateId, + type: productTemplate.productKey, + projectId, + phaseId: newProjectPhase.id, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }) + .then((phaseProduct) => { + newProjectPhase.products = [ + _.omit(phaseProduct.toJSON(), ['deletedAt', 'deletedBy']), + ]; + }); + }); + }), + ) + .then(() => { + // Send events to buses + req.log.debug('Sending event to RabbitMQ bus for project phase %d', newProjectPhase.id); + req.app.services.pubsub.publish(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, + { added: newProjectPhase, route: TIMELINE_REFERENCES.WORK }, + { correlationId: req.id }, + ); + + req.log.debug('Sending event to Kafka bus for project phase %d', newProjectPhase.id); + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, + RESOURCES.PHASE, + newProjectPhase, + ); + + res.status(201).json(newProjectPhase); + }) + .catch((err) => { + util.handleError('Error creating work', err, req, next); + }); + }, +]; diff --git a/src/routes/works/create.spec.js b/src/routes/works/create.spec.js new file mode 100644 index 00000000..8a57ac95 --- /dev/null +++ b/src/routes/works/create.spec.js @@ -0,0 +1,355 @@ +/* eslint-disable no-unused-expressions */ +/** + * Tests for create.js + */ + +import _ from 'lodash'; +import chai from 'chai'; +import sinon from 'sinon'; +import request from 'supertest'; +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; +import busApi from '../../services/busApi'; +import { BUS_API_EVENT } from '../../constants'; + +const should = chai.should(); + +const validatePhase = (resJson, expectedPhase) => { + should.exist(resJson); + resJson.name.should.be.eql(expectedPhase.name); + resJson.status.should.be.eql(expectedPhase.status); + resJson.budget.should.be.eql(expectedPhase.budget); + resJson.progress.should.be.eql(expectedPhase.progress); + resJson.details.should.be.eql(expectedPhase.details); +}; + +describe('CREATE work', () => { + let projectId; + let workStreamId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const project = { + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }; + let productTemplateId; + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + models.WorkManagementPermission.create({ + policy: 'work.create', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + projectTemplateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then(() => { + // Create projects + models.Project.create(_.assign(project, { templateId: template.id })) + .then((_project) => { + projectId = _project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]) + .then(() => + models.ProductTemplate.create({ + name: 'name 1', + productKey: 'productKey 1', + category: 'generic', + subCategory: 'generic', + icon: 'http://example.com/icon1.ico', + brief: 'brief 1', + details: 'details 1', + aliases: ['product key 1', 'product_key_1'], + template: { + template1: { + name: 'template 1', + details: { + anyDetails: 'any details 1', + }, + others: ['others 11', 'others 12'], + }, + template2: { + name: 'template 2', + details: { + anyDetails: 'any details 2', + }, + others: ['others 21', 'others 22'], + }, + }, + createdBy: 1, + updatedBy: 2, + }).then((productTemplate) => { + productTemplateId = productTemplate.id; + done(); + }), + ); + }); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('PATCH /projects/{projectId}/workstreams/{workStreamId}/works', () => { + const body = { + name: 'test project phase', + description: 'test project phase description', + requirements: 'test project phase requirements', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + spentBudget: 10.0, + duration: 10, + details: { + message: 'This can be any json', + }, + }; + + it('should return 403 if user is not authenticated', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .send(body) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .send(body) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 404 for non-existed work stream', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/1234/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect(404, done); + }); + + it('should return 404 for deleted work stream', (done) => { + models.WorkStream.destroy({ where: { id: workStreamId } }) + .then(() => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect(404, done); + }); + }); + + it('should return 400 when name not provided', (done) => { + const reqBody = _.cloneDeep(body); + delete reqBody.name; + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(reqBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 when status not provided', (done) => { + const reqBody = _.cloneDeep(body); + delete reqBody.status; + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(reqBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 when startDate > endDate', (done) => { + const reqBody = _.cloneDeep(body); + reqBody.startDate = '2018-05-16T12:00:00'; + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(reqBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 when budget is negative', (done) => { + const reqBody = _.cloneDeep(body); + reqBody.budget = -20; + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(reqBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 when progress is negative', (done) => { + const reqBody = _.cloneDeep(body); + reqBody.progress = -20; + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(reqBody) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 201 for member', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(_.assign({ productTemplateId }, body)) + .expect(201, done); + }); + + it('should return 201 for connect admin', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .send(_.assign({ productTemplateId }, body)) + .expect(201) + .end((err, res) => { + const resJson = res.body; + validatePhase(resJson, body); + done(); + }); + }); + + describe('Bus api', () => { + let createEventSpy; + const sandbox = sinon.sandbox.create(); + + 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'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_CREATED when work added', (done) => { + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, + sinon.match({ name: body.name })).should.be.true; + done(); + }); + } + }); + }); + }); + }); +}); diff --git a/src/routes/works/delete.js b/src/routes/works/delete.js new file mode 100644 index 00000000..6102db20 --- /dev/null +++ b/src/routes/works/delete.js @@ -0,0 +1,73 @@ +/** + * API to delete a work + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import { EVENT, TIMELINE_REFERENCES } from '../../constants'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('work.delete'), + (req, res, next) => { + const projectId = req.params.projectId; + models.sequelize.transaction(() => + models.PhaseWorkStream.findOne({ + where: { + phaseId: req.params.id, + workStreamId: req.params.workStreamId, + }, + }) + .then((work) => { + // Not found + if (!work) { + const apiErr = new Error(`work not found for work stream id ${req.params.workStreamId} ` + + `and work id ${req.params.id}`); + apiErr.status = 404; + throw apiErr; + } + + return models.ProjectPhase.findOne({ + where: { + id: req.params.id, + projectId, + }, + }); + }) + .then((entity) => { + if (!entity) { + const apiErr = new Error(`work not found for work stream id ${req.params.workStreamId}, ` + + `project id ${projectId} and work id ${req.params.id}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + // Update the deletedBy, then delete + return entity.update({ deletedBy: req.authUser.userId }); + }) + .then(entity => entity.destroy())) + .then((deleted) => { + req.log.debug('deleted work', JSON.stringify(deleted, null, 2)); + + // Send events to buses + req.app.services.pubsub.publish( + EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, + { deleted, route: TIMELINE_REFERENCES.WORK }, + { correlationId: req.id }, + ); + req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, { req, deleted }); + + res.status(204).json({}); + }).catch(err => next(err)); + }, +]; diff --git a/src/routes/works/delete.spec.js b/src/routes/works/delete.spec.js new file mode 100644 index 00000000..39879bfa --- /dev/null +++ b/src/routes/works/delete.spec.js @@ -0,0 +1,288 @@ +/* eslint-disable no-unused-expressions */ +/** + * Tests for delete.js + */ +import _ from 'lodash'; +import request from 'supertest'; +import chai from 'chai'; +import sinon from 'sinon'; +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; +import busApi from '../../services/busApi'; +import { BUS_API_EVENT } from '../../constants'; + +chai.should(); + +const expectAfterDelete = (workId, projectId, workStreamId, err, next) => { + if (err) throw err; + setTimeout(() => + models.ProjectPhase.findOne({ + where: { + id: workId, + }, + paranoid: false, + }) + .then((res) => { + if (!res) { + throw new Error('Should found the entity'); + } else { + chai.assert.isNotNull(res.deletedAt); + chai.assert.isNotNull(res.deletedBy); + + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, next); + } + }), 500); +}; + +describe('DELETE work', () => { + let projectId; + let workStreamId; + let workId; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const project = { + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + models.WorkManagementPermission.create({ + policy: 'work.delete', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + projectTemplateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then(() => { + // Create projects + models.Project.create(_.assign(project, { templateId: template.id })) + .then((_project) => { + projectId = _project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + models.ProjectPhase.create({ + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + projectId, + }).then((phase) => { + workId = phase.id; + models.PhaseWorkStream.create({ + phaseId: workId, + workStreamId, + }).then(() => { + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]).then(() => done()); + }); + }); + }); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('DELETE /projects/{projectId}/workstreams/{workStreamId}/works/{workId}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 404 when no work stream with specific workStreamId', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/999/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work with specific workId', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 for deleted type', (done) => { + models.ProjectPhase.destroy({ where: { id: workId } }) + .then(() => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + }); + + it('should return 204 for member', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(204) + .end(err => expectAfterDelete(workId, projectId, workStreamId, err, done)); + }); + + it('should return 204, for admin, if type was successfully removed', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end(err => expectAfterDelete(workId, projectId, workStreamId, err, done)); + }); + + it('should return 204, for connect admin, if type was successfully removed', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(204) + .end(err => expectAfterDelete(workId, projectId, workStreamId, err, done)); + }); + + describe('Bus api', () => { + let createEventSpy; + const sandbox = sinon.sandbox.create(); + + 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'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_DELETED when work removed', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(204) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED).should.be.true; + done(); + }); + } + }); + }); + }); + }); +}); diff --git a/src/routes/works/get.js b/src/routes/works/get.js new file mode 100644 index 00000000..b94b974a --- /dev/null +++ b/src/routes/works/get.js @@ -0,0 +1,58 @@ +/** + * API to get a work + */ +import validate from 'express-validation'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('work.view'), + (req, res, next) => models.PhaseWorkStream.findOne({ + where: { + phaseId: req.params.id, + workStreamId: req.params.workStreamId, + }, + // attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }) + .then((work) => { + // Not found + if (!work) { + const apiErr = new Error(`work not found for work stream id ${req.params.workStreamId}, ` + + `project id ${req.params.projectId} and work id ${req.params.id}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + return models.ProjectPhase.findOne({ + where: { + id: req.params.id, + projectId: req.params.projectId, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + }); + }) + .then((phase) => { + // Not found + if (!phase) { + const apiErr = new Error(`work not found for work stream id ${req.params.workStreamId}, ` + + `project id ${req.params.projectId} and work id ${req.params.id}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } + + return res.json(phase); + }) + .catch(next), +]; diff --git a/src/routes/works/get.spec.js b/src/routes/works/get.spec.js new file mode 100644 index 00000000..768fd785 --- /dev/null +++ b/src/routes/works/get.spec.js @@ -0,0 +1,216 @@ +/** + * Tests for get.js + */ +import chai from 'chai'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('GET work', () => { + const body = { + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + }; + + let projectId; + let workStreamId; + let workId; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + models.ProjectPhase.create({ + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + projectId, + }).then((phase) => { + workId = phase.id; + models.PhaseWorkStream.create({ + phaseId: workId, + workStreamId, + }).then(() => done()); + }); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/workstreams/{workStreamId}/works/{workId}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 404 when no project with specific projectId', (done) => { + request(server) + .get(`/v5/projects/9999/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work stream with specific workStreamId', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/999/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work with specific workId', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 for deleted type', (done) => { + models.ProjectPhase.destroy({ where: { id: workId } }) + .then(() => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, done); + }); + }); + + it('should return 200 for admin', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.name.should.be.eql(body.name); + resJson.status.should.be.eql(body.status); + resJson.budget.should.be.eql(body.budget); + resJson.progress.should.be.eql(body.progress); + resJson.details.should.be.eql(body.details); + resJson.createdBy.should.be.eql(body.createdBy); + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(body.updatedBy); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + done(); + }); + }); + + it('should return 200 for connect admin', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(200) + .end(done); + }); + + it('should return 200 for connect manager', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(200) + .end(done); + }); + }); +}); diff --git a/src/routes/works/list.js b/src/routes/works/list.js new file mode 100644 index 00000000..78f00f03 --- /dev/null +++ b/src/routes/works/list.js @@ -0,0 +1,96 @@ +/** + * API to list all works + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; +import models from '../../models'; + +const PHASE_ATTRIBUTES = _.keys(models.ProjectPhase.rawAttributes); +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + }, +}; + +module.exports = [ + validate(schema), + permissions('work.view'), + (req, res, next) => { + const workStreamId = req.params.workStreamId; + const projectId = req.params.projectId; + + // Parse the fields string to determine what fields are to be returned + const rawFields = req.query.fields ? decodeURIComponent(req.query.fields).split(',') : PHASE_ATTRIBUTES; + let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'startDate'; + if (sort && sort.indexOf(' ') === -1) { + sort += ' asc'; + } + const sortableProps = [ + 'startDate asc', 'startDate desc', + 'endDate asc', 'endDate desc', + 'status asc', 'status desc', + 'order asc', 'order desc', + ]; + if (sort && _.indexOf(sortableProps, sort) < 0) { + return util.handleError('Invalid sort criteria', null, req, next); + } + + const sortParameters = sort.split(' '); + + const fields = _.union( + _.intersection(rawFields, [...PHASE_ATTRIBUTES, 'workItems']), + ['id'], // required fields + ); + + // search condition for ProjectPhase + const include = { + model: models.ProjectPhase, + through: { attributes: [] }, + where: { + projectId, + }, + attributes: fields.filter(f => f !== 'workItems'), + required: false, + }; + if (fields.includes('workItems')) { + _.set(include, 'include', [{ model: models.PhaseProduct, as: 'products' }]); + } + + return models.WorkStream.findOne({ + where: { + id: workStreamId, + projectId, + deletedAt: { $eq: null }, + }, + include: [include], + order: [[models.ProjectPhase, sortParameters[0], sortParameters[1]]], + }) + .then((existingWorkStream) => { + if (!existingWorkStream) { + // handle 404 + const err = new Error(`active work stream not found for project id ${projectId} ` + + `and work stream id ${workStreamId}`); + err.status = 404; + throw err; + } + + // rename 'products' to 'workItems' + return existingWorkStream.ProjectPhases.map((phase) => { + const phaseObj = phase.get({ plain: true }); + if (_.has(phaseObj, 'products')) { + _.set(phaseObj, 'workItems', _.get(phaseObj, 'products')); + _.unset(phaseObj, 'products'); + } + return phaseObj; + }); + }) + .then(phases => res.json(phases)) + .catch(next); + }, +]; diff --git a/src/routes/works/list.spec.js b/src/routes/works/list.spec.js new file mode 100644 index 00000000..4edc1796 --- /dev/null +++ b/src/routes/works/list.spec.js @@ -0,0 +1,224 @@ +/** + * Tests for list.js + */ +import chai from 'chai'; +import _ from 'lodash'; +import request from 'supertest'; + +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; + +const should = chai.should(); + +describe('LIST works', () => { + const phases = [ + { + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + }, + { + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + }, + ]; + + const productBody = { + name: 'test phase product', + type: 'product1', + estimatedPrice: 20.0, + actualPrice: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + }; + + + let projectId; + let workStreamId; + + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + // Create projects + models.Project.create({ + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + templateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then((project) => { + projectId = project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + models.ProjectPhase.bulkCreate(_.map(phases, p => _.assign(p, { projectId })), + { returning: true }) + .then((p) => { + models.PhaseWorkStream.bulkCreate([{ + phaseId: p[0].id, + workStreamId, + }, { + phaseId: p[1].id, + workStreamId, + }]).then((ws) => { + _.assign(productBody, { phaseId: ws[0].phaseId, projectId }); + + models.PhaseProduct.create(productBody).then(() => { + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('GET /projects/{projectId}/workstreams/{workStreamId}/works', () => { + it('should return 200 for admin', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const phase = phases[0]; + + const resJson = res.body; + resJson.should.have.length(2); + resJson[0].name.should.be.eql(phase.name); + resJson[0].status.should.be.eql(phase.status); + resJson[0].budget.should.be.eql(phase.budget); + resJson[0].progress.should.be.eql(phase.progress); + resJson[0].details.should.be.eql(phase.details); + should.exist(resJson[0].createdAt); + resJson[0].updatedBy.should.be.eql(phase.updatedBy); + should.exist(resJson[0].updatedAt); + should.not.exist(resJson[0].deletedBy); + should.not.exist(resJson[0].deletedAt); + + done(); + }); + }); + + it('should return 403 if user is not authenticated', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .expect(403, done); + }); + + it('should return 403 for member', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403, done); + }); + + it('should return 200 for connect admin', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(200) + .end(done); + }); + + it('should return 200 for connect manager', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(200) + .end(done); + }); + + it('should return with populated workItems if fields=workItems is used', (done) => { + request(server) + .get(`/v5/projects/${projectId}/workstreams/${workStreamId}/works?fields=workItems`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.should.have.length(2); + resJson[0].should.have.property('workItems'); + resJson[0].workItems.should.be.a('array'); + resJson[0].workItems.should.have.lengthOf(1); + resJson[0].workItems[0].should.have.property('projectId'); + resJson[0].workItems[0].projectId.should.equal(projectId); + resJson[0].workItems[0].name.should.equal(productBody.name); + resJson[1].should.have.property('workItems'); + resJson[1].workItems.should.be.a('array'); + resJson[1].workItems.should.have.lengthOf(0); + done(); + }); + }); + }); +}); diff --git a/src/routes/works/update.js b/src/routes/works/update.js new file mode 100644 index 00000000..4042aad0 --- /dev/null +++ b/src/routes/works/update.js @@ -0,0 +1,189 @@ +/** + * API to update work + */ +import validate from 'express-validation'; +import _ from 'lodash'; +import Joi from 'joi'; +import Sequelize from 'sequelize'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import models from '../../models'; +import util from '../../util'; +import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants'; + +const permissions = tcMiddleware.permissions; + +const schema = { + params: { + projectId: Joi.number().integer().positive().required(), + workStreamId: Joi.number().integer().positive().required(), + id: Joi.number().integer().positive().required(), + }, + body: Joi.object().keys({ + name: Joi.string().optional(), + description: Joi.string().optional(), + requirements: Joi.string().optional(), + status: Joi.string().optional(), + startDate: Joi.date().optional(), + endDate: Joi.date().optional(), + duration: Joi.number().min(0).optional(), + budget: Joi.number().min(0).optional(), + spentBudget: Joi.number().min(0).optional(), + progress: Joi.number().min(0).optional(), + details: Joi.any().optional(), + order: Joi.number().integer().optional(), + }).required(), +}; + + +module.exports = [ + // validate request payload + validate(schema), + // check permission + permissions('work.edit'), + + (req, res, next) => { + const projectId = _.parseInt(req.params.projectId); + const workStreamId = _.parseInt(req.params.workStreamId); + const phaseId = _.parseInt(req.params.id); + + const updatedProps = req.body; + updatedProps.updatedBy = req.authUser.userId; + + let previousValue; + let updated; + + models.sequelize.transaction(() => models.ProjectPhase.findOne({ + where: { + id: phaseId, + projectId, + }, + include: [{ + model: models.WorkStream, + through: { attributes: [] }, + where: { + id: workStreamId, + projectId, + }, + }], + }) + .then((existing) => { + if (!existing) { + // handle 404 + const err = new Error('No active project phase found for project id ' + + `${projectId} and work stream ${workStreamId} and phase id ${phaseId}`); + err.status = 404; + throw err; + } else { + previousValue = _.clone(existing.get({ plain: true })); + + // make sure startDate < endDate + let startDate; + let endDate; + if (updatedProps.startDate) { + startDate = new Date(updatedProps.startDate); + } else { + startDate = existing.startDate !== null ? new Date(existing.startDate) : null; + } + + if (updatedProps.endDate) { + endDate = new Date(updatedProps.endDate); + } else { + endDate = existing.endDate !== null ? new Date(existing.endDate) : null; + } + + if (startDate !== null && endDate !== null && startDate > endDate) { + const err = new Error('startDate must not be after endDate.'); + err.status = 400; + throw err; + } else { + _.extend(existing, updatedProps); + return existing.save().catch(next); + } + } + }) + .then((updatedPhase) => { + updated = updatedPhase; + // Ignore re-ordering if there's no order specified for this phase + if (_.isNil(updated.order)) { + return Promise.resolve(); + } + + // Update order of the other phases only if the order was changed + if (previousValue.order === updated.order) { + return Promise.resolve(); + } + + return models.ProjectPhase.count({ + where: { + id: { $ne: phaseId }, + }, + include: [{ + model: models.WorkStream, + where: { + id: workStreamId, + projectId, + }, + }], + }) + .then((count) => { + if (count === 0) { + return Promise.resolve(); + } + + // Increase the order from M to K: if there is an item with order K, + // orders from M+1 to K should be made M to K-1 + if (!_.isNil(previousValue.order) && previousValue.order < updated.order) { + return models.ProjectPhase.update({ order: Sequelize.literal('"order" - 1') }, { + where: { + projectId, + id: { $ne: updated.id }, + order: { $between: [previousValue.order + 1, updated.order] }, + }, + }); + } + + // Decrease the order from M to K: if there is an item with order K, + // orders from K to M-1 should be made K+1 to M + return models.ProjectPhase.update({ order: Sequelize.literal('"order" + 1') }, { + where: { + projectId, + id: { $ne: updated.id }, + order: { + $between: [ + updated.order, + (previousValue.order ? previousValue.order : Number.MAX_SAFE_INTEGER) - 1, + ], + }, + }, + }); + }); + }) + .then(() => + // To simpify the logic, reload the phases from DB and send to the message queue + models.ProjectPhase.findAll({ + where: { + projectId, + }, + include: [{ model: models.PhaseProduct, as: 'products' }], + })), + ) + .then((allPhases) => { + req.log.debug('updated project phase', JSON.stringify(updated, null, 2)); + + // emit original and updated project phase information + req.app.services.pubsub.publish( + EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, + { original: previousValue, updated, allPhases, route: TIMELINE_REFERENCES.WORK }, + { correlationId: req.id }, + ); + util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, + RESOURCES.PHASE, + _.clone(updated.get({ plain: true })), + ); + + res.json(updated); + }) + .catch(err => next(err)); + }, +]; diff --git a/src/routes/works/update.spec.js b/src/routes/works/update.spec.js new file mode 100644 index 00000000..f105c999 --- /dev/null +++ b/src/routes/works/update.spec.js @@ -0,0 +1,426 @@ +/* eslint-disable no-unused-expressions */ +/** + * Tests for update.js + */ +import _ from 'lodash'; +import chai from 'chai'; +import request from 'supertest'; +import sinon from 'sinon'; +import models from '../../models'; +import server from '../../app'; +import testUtil from '../../tests/util'; +import busApi from '../../services/busApi'; +import { BUS_API_EVENT } from '../../constants'; + +const should = chai.should(); + +const body = { + name: 'test project phase', + description: 'test project phase description', + requirements: 'test project phase requirements', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, +}; + +const updateBody = { + name: 'test project phase xxx', + description: 'test project phase description xxx', + requirements: 'test project phase requirements xxx', + status: 'inactive', + startDate: '2018-05-11T00:00:00Z', + endDate: '2018-05-12T12:00:00Z', + budget: 123456.789, + progress: 9.8765432, + details: { + message: 'This is another json', + }, +}; + +const validatePhase = (resJson, expectedPhase) => { + should.exist(resJson); + resJson.name.should.be.eql(expectedPhase.name); + resJson.status.should.be.eql(expectedPhase.status); + resJson.budget.should.be.eql(expectedPhase.budget); + resJson.progress.should.be.eql(expectedPhase.progress); + resJson.details.should.be.eql(expectedPhase.details); +}; + +describe('UPDATE work', () => { + let projectId; + let workStreamId; + let workId; + let workId2; + + const memberUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const copilotUser = { + handle: testUtil.getDecodedToken(testUtil.jwts.copilot).handle, + userId: testUtil.getDecodedToken(testUtil.jwts.copilot).userId, + firstName: 'fname', + lastName: 'lName', + email: 'some@abc.com', + }; + const project = { + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }; + beforeEach((done) => { + testUtil.clearDb() + .then(() => { + models.ProjectTemplate.create({ + name: 'template 2', + key: 'key 2', + category: 'category 2', + icon: 'http://example.com/icon1.ico', + question: 'question 2', + info: 'info 2', + aliases: ['key-2', 'key_2'], + scope: {}, + phases: {}, + createdBy: 1, + updatedBy: 2, + }) + .then((template) => { + models.WorkManagementPermission.create({ + policy: 'work.edit', + permission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['Connect Manager', 'Connect Admin', 'administrator'], + }, + denyRule: { projectRoles: ['copilot'] }, + }, + projectTemplateId: template.id, + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }) + .then(() => { + // Create projects + models.Project.create(_.assign(project, { templateId: template.id })) + .then((_project) => { + projectId = _project.id; + models.WorkStream.create({ + name: 'Work Stream', + type: 'generic', + status: 'active', + projectId, + createdBy: 1, + updatedBy: 1, + }).then((entity) => { + workStreamId = entity.id; + _.assign(body, { projectId }); + const createPhases = [ + body, + _.assign({ order: 1 }, body), + _.assign({}, body, { status: 'draft' }), + ]; + models.ProjectPhase.bulkCreate(createPhases, { returning: true }).then((phases) => { + workId = phases[0].id; + workId2 = phases[1].id; + models.PhaseWorkStream.bulkCreate([{ + phaseId: phases[0].id, + workStreamId, + }, { + phaseId: phases[1].id, + workStreamId, + }, { + phaseId: phases[2].id, + workStreamId, + }]).then(() => { + // create members + models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]).then(() => done()); + }); + }); + }); + }); + }); + }); + }); + }); + + after((done) => { + testUtil.clearDb(done); + }); + + describe('PATCH /projects/{projectId}/workstreams/{workStreamId}/works/{workId}', () => { + it('should return 403 if user is not authenticated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .send(updateBody) + .expect(403, done); + }); + + it('should return 403 for copilot', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(updateBody) + .expect(403, done); + }); + + it('should return 404 when no work stream with specific workStreamId', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/999/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 404 when no work with specific workId', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/999`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(404, done); + }); + + it('should return 400 when parameters are invalid', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + progress: -15, + }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 400 when startDate > endDate', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send({ + endDate: '2018-05-13T00:00:00Z', + }) + .expect('Content-Type', /json/) + .expect(400, done); + }); + + it('should return 200 for member', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send(updateBody) + .expect(200, done); + }); + + it('should return updated phase when user have permission and parameters are valid', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + validatePhase(resJson, updateBody); + done(); + } + }); + }); + + it('should return updated phase when parameters are valid (0 for non -ve numbers)', (done) => { + const bodyWithZeros = _.cloneDeep(updateBody); + bodyWithZeros.duration = 0; + bodyWithZeros.spentBudget = 0.0; + bodyWithZeros.budget = 0.0; + bodyWithZeros.progress = 0.0; + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(bodyWithZeros) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + validatePhase(resJson, bodyWithZeros); + done(); + } + }); + }); + + it('should return updated phase if the order is specified', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(_.assign({ order: 1 }, updateBody)) + .expect('Content-Type', /json/) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + validatePhase(resJson, updateBody); + resJson.order.should.be.eql(1); + + // Check the order of the other phase + models.ProjectPhase.findOne({ where: { id: workId2 } }) + .then((work2) => { + work2.order.should.be.eql(2); + done(); + }); + } + }); + }); + + describe('Bus api', () => { + let createEventSpy; + const sandbox = sinon.sandbox.create(); + + 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'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when duration updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + duration: 100, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, + sinon.match({ duration: 100 })).should.be.true; + done(); + }); + } + }); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when order updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + order: 100, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, + sinon.match({ order: 100 })).should.be.true; + done(); + }); + } + }); + }); + + it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when endDate updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + endDate: new Date(), + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, + sinon.match({ name: body.name })).should.be.true; + done(); + }); + } + }); + }); + }); + }); +}); diff --git a/src/services/messageService.js b/src/services/messageService.js index 949c0134..c596dddf 100644 --- a/src/services/messageService.js +++ b/src/services/messageService.js @@ -1,6 +1,5 @@ import config from 'config'; import _ from 'lodash'; -// import util from '../util'; const Promise = require('bluebird'); const axios = require('axios'); @@ -141,19 +140,19 @@ function deletePosts(topicId, postIds, logger) { * Fetches the topic of given phase of the project. * * @param {Integer} projectId id of the project - * @param {Integer} phaseId id of the phase of the project + * @param {String} tag tag * @param {Object} logger object * @return {Promise} topic promise */ -function getPhaseTopic(projectId, phaseId, logger) { - logger.debug(`getPhaseTopic for projectId: ${projectId} phaseId: ${phaseId}`); +function getTopicByTag(projectId, tag, logger) { + logger.debug(`getTopicByTag for projectId: ${projectId} tag: ${tag}`); return getClient(logger).then((msgClient) => { - logger.debug(`calling message service for fetching phaseId#${phaseId}`); - const encodedFilter = encodeURIComponent(`reference=project&referenceId=${projectId}&tag=phase#${phaseId}`); + logger.debug(`calling message service for fetching ${tag}`); + const encodedFilter = encodeURIComponent(`reference=project&referenceId=${projectId}&tag=${tag}`); return msgClient.get(`/topics/list/db?filter=${encodedFilter}`) .then((resp) => { - logger.debug('Fetched phase topic', resp); const topics = _.get(resp.data, 'result.content', []); + logger.debug(`Fetched ${topics.length} topics`); if (topics && topics.length > 0) { return topics[0]; } @@ -181,6 +180,7 @@ module.exports = { createTopic, updateTopic, deletePosts, - getPhaseTopic, + getTopicByTag, deleteTopic, + getClient, }; diff --git a/src/tests/mockRabbitMQ.js b/src/tests/mockRabbitMQ.js new file mode 100644 index 00000000..3913bd73 --- /dev/null +++ b/src/tests/mockRabbitMQ.js @@ -0,0 +1,18 @@ +/** + * Mock RabbitMQ service + */ +/* globals Promise */ + +import sinon from 'sinon'; +import _ from 'lodash'; + +module.exports = (app) => { + _.assign(app.services, { + pubsub: { + init: () => {}, + publish: () => {}, + }, + }); + sinon.stub(app.services.pubsub, 'init', () => Promise.resolve(true)); + sinon.stub(app.services.pubsub, 'publish', () => Promise.resolve(true)); +}; diff --git a/src/util.js b/src/util.js index d8210cda..2740c0dd 100644 --- a/src/util.js +++ b/src/util.js @@ -11,22 +11,25 @@ import _ from 'lodash'; +import querystring from 'querystring'; import config from 'config'; import urlencode from 'urlencode'; import elasticsearch from 'elasticsearch'; import Promise from 'bluebird'; +import models from './models'; // import AWS from 'aws-sdk'; -import { ADMIN_ROLES, TOKEN_SCOPES, EVENT, PROJECT_MEMBER_ROLE } from './constants'; +import { ADMIN_ROLES, TOKEN_SCOPES, EVENT, PROJECT_MEMBER_ROLE, VALUE_TYPE, ESTIMATION_TYPE } from './constants'; const exec = require('child_process').exec; -const models = require('./models').default; const tcCoreLibAuth = require('tc-core-library-js').auth; const m2m = tcCoreLibAuth.m2m(config); const util = _.cloneDeep(require('tc-core-library-js').util(config)); +const ssoRefCodes = JSON.parse(config.get('SSO_REFCODES')); + // the client modifies the config object, so always passed the cloned object let esClient = null; @@ -77,6 +80,76 @@ _.assignIn(util, { }); return valid; }, + /** + * Calculate project estimation item price + * @param {object} valueType value type can be int, string, double, percentage + * @param {String} value value + * @param {Double} price price + * @return {Double|String} calculated price value + */ + calculateEstimationItemPrice: (valueType, value, price) => { + if (valueType === VALUE_TYPE.PERCENTAGE) { + return (value * price) / 100; + } + return value; + }, + /** + * Calculate project estimation item price + * @param {Object} req the request + * @param {Number} projectId project id + * @return {Array} estimation items + */ + calculateProjectEstimationItems: (req, projectId) => + // delete ALL existent ProjectEstimationItems for the project + models.ProjectEstimationItem.deleteAllForProject(models, projectId, req.authUser, { + includeAllProjectEstimatinoItemsForInternalUsage: true, + }) + + // retrieve ProjectSettings and ProjectEstimations + .then(() => Promise.all([ + models.ProjectSetting.findAll({ + includeAllProjectSettingsForInternalUsage: true, + where: { + projectId, + key: _.map(_.values(ESTIMATION_TYPE), type => `markup_${type}`), + }, + raw: true, + }), + models.ProjectEstimation.findAll({ + where: { projectId: req.params.projectId }, + raw: true, + }), + ])) + + // create ProjectEstimationItems + .then(([settings, estimations]) => { + if (!settings || settings.length === 0) { + req.log.debug('No project settings for prices found, therefore no estimation items are created'); + return []; + } + + if (!estimations || estimations.length === 0) { + req.log.debug('No price estimations found, therefore no estimation items are created'); + return []; + } + + const estimationItems = []; + _.each(estimations, (estimation) => { + _.each(settings, (setting) => { + estimationItems.push({ + projectEstimationId: estimation.id, + price: util.calculateEstimationItemPrice(setting.valueType, setting.value, estimation.price), + type: setting.key.replace(/^markup_/, ''), + markupUsedReference: 'projectSetting', + markupUsedReferenceId: setting.id, + createdBy: req.authUser.userId, + updatedBy: req.authUser.userId, + }); + }); + }); + + return models.ProjectEstimationItem.bulkCreate(estimationItems); + }), /** * Helper funtion to verify if user has specified role * @param {object} req Request object that should contain authUser @@ -165,6 +238,26 @@ _.assignIn(util, { return fields; }, + /** + * Parse the query filters + * @param {String} fqueryFilter the query filter string + * @return {Object} the parsed array + */ + parseQueryFilter: (fqueryFilter) => { + let queryFilter = querystring.parse(fqueryFilter); + // convert in to array + queryFilter = _.mapValues(queryFilter, (val) => { + if (val.indexOf('in(') > -1) { + return { $in: val.substring(3, val.length - 1).split(',') }; + } + return val; + }); + if (queryFilter.id && queryFilter.id.$in) { + queryFilter.id.$in = _.map(queryFilter.id.$in, _.parseInt); + } + return queryFilter; + }, + /** * Moves file from source to destination * @param {object} req request object @@ -517,6 +610,72 @@ _.assignIn(util, { }); }, + /** + * Lookup user handles from multiple emails + * @param {Object} req request + * @param {Array} userEmails user emails + * @param {Number} maximumRequests limit number of request on one batch + * @param {Boolean} isPattern flag to indicate that pattern matching is required or not + * @return {Promise} promise + */ + lookupMultipleUserEmails(req, userEmails, maximumRequests, isPattern = false) { + req.log.debug(`identityServiceEndpoint: ${config.get('identityServiceEndpoint')}`); + + const httpClient = util.getHttpClient({ id: req.id, log: req.log }); + // request generator function + const generateRequest = ({ token, email }) => { + let filter = `email=${email}`; + if (isPattern) { + filter += '&like=true'; + } + return httpClient.get(`${config.get('identityServiceEndpoint')}users`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + params: { + fields: 'handle,id,email', + filter, + }, + // set longer timeout as default 3000 could be not enough for identity service response + timeout: 15000, + }).catch(() => { + // in case of any error happens during getting user by email + // we treat such users as not found and don't return error + // as per discussion in issue #334 + }); + }; + // send batch of requests, one batch at one time + const sendBatch = (options) => { + const token = options.token; + const emails = options.emails; + const users = options.users || []; + const batch = options.batch || 0; + const start = batch * maximumRequests; + const end = (batch + 1) * maximumRequests; + const requests = emails.slice(start, end).map(userEmail => + generateRequest({ token, email: userEmail })); + return Promise.all(requests) + .then((responses) => { + const data = responses.reduce((contents, response) => { + const content = _.get(response, 'data.result.content', []); + return _.concat(contents, content); + }, users); + req.log.debug(`UserHandle response batch-${batch}`, data); + if (end < emails.length) { + return sendBatch({ token, users: data, emails, batch: batch + 1 }); + } + return data; + }); + }; + return util.getM2MToken() + .then((m2mToken) => { + req.log.debug(`Bearer ${m2mToken}`); + return sendBatch({ token: m2mToken, emails: userEmails }); + }); + }, + /** * Filter only members of topcoder team * @param {Array} members project members @@ -524,6 +683,13 @@ _.assignIn(util, { */ getTopcoderProjectMembers: members => _(members).filter(m => m.role !== PROJECT_MEMBER_ROLE.CUSTOMER), + /** + * Check if project is for SSO users + * @param {Object} project project + * @return {Boolean} is SSO project + */ + isSSO: project => ssoRefCodes.indexOf(_.get(project, 'details.utm.code')) > -1, + /** * Set paginated header and respond with data * @param {Object} req HTTP request @@ -628,6 +794,174 @@ _.assignIn(util, { return Promise.resolve(null); }, + + /** + * Check if user match the permission rule. + * + * This method uses permission rule defined in `permissionRule` + * and checks that the `user` matches it. + * + * If we define a rule with `projectRoles` list, we also should provide `projectMembers` + * - the list of project members. + * + * @param {Object} permissionRule permission rule + * @param {Array} permissionRule.projectRoles the list of project roles of the user + * @param {Array} permissionRule.topcoderRoles the list of Topcoder roles of the user + * @param {Object} user user for whom we check permissions + * @param {Object} user.roles list of user roles + * @param {Object} user.isMachine `true` - if it's machine, `false` - real user + * @param {Object} user.scopes scopes of user token + * @param {Array} projectMembers (optional) list of project members - required to check `topcoderRoles` + * + * @returns {Boolean} true, if has permission + */ + matchPermissionRule: (permissionRule, user, projectMembers) => { + const member = _.find(projectMembers, { userId: user.userId }); + let hasProjectRole = false; + let hasTopcoderRole = false; + + if (permissionRule) { + if (permissionRule.projectRoles + && permissionRule.projectRoles.length > 0 + && !!member + ) { + hasProjectRole = _.includes(permissionRule.projectRoles, member.role); + } + + if (permissionRule.topcoderRoles && permissionRule.topcoderRoles.length > 0) { + hasTopcoderRole = util.hasRoles({ authUser: user }, permissionRule.topcoderRoles); + } + } + + return hasProjectRole || hasTopcoderRole; + }, + + /** + * Check if user has permission. + * + * This method uses permission defined in `permission` and checks that the `user` matches it. + * + * `permission` may be defined in two ways: + * - **Full** way with defined `allowRule` and optional `denyRule`, example: + * ```js + * { + * allowRule: { + * projectRoles: [], + * topcoderRoles: [] + * }, + * denyRule: { + * projectRoles: [], + * topcoderRoles: [] + * } + * } + * ``` + * If user matches `denyRule` then the access would be dined even if matches `allowRule`. + * - **Simplified** way may be used if we only want to define `allowRule`. + * We can skip the `allowRule` property and define `allowRule` directly inside `permission` object, example: + * ```js + * { + * projectRoles: [], + * topcoderRoles: [] + * } + * ``` + * This **simplified** permission is equal to a **full** permission: + * ```js + * { + * allowRule: { + * projectRoles: [], + * topcoderRoles: [] + * } + * } + * ``` + * + * If we define any rule with `projectRoles` list, we also should provide `projectMembers` + * - the list of project members. + * + * @param {Object} permission permission or permissionRule + * @param {Object} user user for whom we check permissions + * @param {Object} user.roles list of user roles + * @param {Object} user.isMachine `true` - if it's machine, `false` - real user + * @param {Object} user.scopes scopes of user token + * @param {Array} projectMembers (optional) list of project members - required to check `topcoderRoles` + * + * @returns {Boolean} true, if has permission + */ + hasPermission: (permission, user, projectMembers) => { + const allowRule = permission.allowRule ? permission.allowRule : permission; + const denyRule = permission.denyRule ? permission.denyRule : null; + + const allow = util.matchPermissionRule(allowRule, user, projectMembers); + const deny = util.matchPermissionRule(denyRule, user, projectMembers); + + return allow && !deny; + }, + + /** + * Check if user has permission for the project by `projectId`. + * + * This method uses permission defined in `permission` and checks that the `user` matches it. + * + * `permission` may be defined in two ways: + * - **Full** way with defined `allowRule` and optional `denyRule`, example: + * ```js + * { + * allowRule: { + * projectRoles: [], + * topcoderRoles: [] + * }, + * denyRule: { + * projectRoles: [], + * topcoderRoles: [] + * } + * } + * ``` + * If user matches `denyRule` then the access would be dined even if matches `allowRule`. + * - **Simplified** way may be used if we only want to define `allowRule`. + * We can skip the `allowRule` property and define `allowRule` directly inside `permission` object, example: + * ```js + * { + * projectRoles: [], + * topcoderRoles: [] + * } + * ``` + * This **simplified** permission is equal to a **full** permission: + * ```js + * { + * allowRule: { + * projectRoles: [], + * topcoderRoles: [] + * } + * } + * ``` + * + * @param {Object} permission permission or permissionRule + * @param {Object} user user for whom we check permissions + * @param {Object} user.roles list of user roles + * @param {Object} user.isMachine `true` - if it's machine, `false` - real user + * @param {Object} user.scopes scopes of user token + * @param {Number} projectId project id to check permissions for + * + * @returns {Promise} true, if has permission + */ + hasPermissionForProject: (permission, user, projectId) => ( + models.ProjectMember.getActiveProjectMembers(projectId).then(projectMembers => + util.hasPermission(permission, user, projectMembers), + ) + ), + + /** + * Checks if the Project Setting represents price estimation setting + * + * @param {String} key project setting key + * + * @returns {Boolean} true it's project setting for price estimation + */ + isProjectSettingForEstimation: (key) => { + const markupMatch = key.match(/^markup_(.+)$/); + const markupKey = markupMatch && markupMatch[1] ? markupMatch[1] : null; + + return markupKey ? _.includes(_.values(ESTIMATION_TYPE), markupKey) : false; + }, }); export default util; From f12979ac97489691a61447f9d4dbd2b157b98bab Mon Sep 17 00:00:00 2001 From: gets0ul Date: Fri, 18 Oct 2019 00:04:18 +0700 Subject: [PATCH 07/88] Added fixes. --- .circleci/config.yml | 3 +- local/seed/seedMetadata.js | 40 +++++++++++++++++++++- local/seed/seedProjects.js | 15 +++++++++ src/models/form.js | 1 + src/models/projectMemberInvite.js | 5 ++- src/models/projectTemplate.js | 13 +++++++ src/routes/admin/project-index-create.js | 1 + src/routes/form/version/getVersion.js | 10 +----- src/routes/milestones/delete.js | 5 +-- src/routes/milestones/delete.spec.js | 11 ++++-- src/routes/milestones/get.spec.js | 9 +++++ src/routes/productTemplates/list.js | 43 ++++++++++-------------- src/routes/productTemplates/list.spec.js | 8 ++--- src/routes/projectTemplates/list.js | 34 +++++++------------ src/routes/projectTemplates/list.spec.js | 2 +- src/routes/projects/list.spec.js | 3 +- src/routes/timelines/create.js | 21 ++---------- src/routes/timelines/create.spec.js | 9 +++++ src/routes/timelines/delete.spec.js | 2 ++ src/routes/timelines/get.spec.js | 10 ++++++ src/routes/timelines/update.spec.js | 15 +++++++++ src/tests/serviceMocks.js | 9 ++--- src/tests/util.js | 3 ++ src/util.spec.js | 39 +++++++++++++++++++++ 24 files changed, 216 insertions(+), 95 deletions(-) create mode 100644 src/util.spec.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 4864453b..84f1cf7c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,6 +48,7 @@ jobs: - POSTGRES_USER: circle_test - POSTGRES_DB: circle_test - image: elasticsearch:2.3 + - image: rabbitmq:3-management environment: DB_MASTER_URL: postgres://circle_test:@127.0.0.1:5432/circle_test AUTH_SECRET: secret @@ -96,7 +97,7 @@ workflows: - test filters: branches: - only: ['dev', 'dev-sts'] + only: ['dev', 'dev-sts', 'feature/looker-api-integration'] - deployProd: context : org-global requires: diff --git a/local/seed/seedMetadata.js b/local/seed/seedMetadata.js index 6cf6cfa8..6af36f21 100644 --- a/local/seed/seedMetadata.js +++ b/local/seed/seedMetadata.js @@ -37,7 +37,45 @@ module.exports = (targetUrl, token) => { 'Authorization': 'Bearer ' + token } - let promises = _(data.result.content.projectTypes).map(pt=>{ + let promises + + promises = _(data.result.content.forms).orderBy(['key', 'asc'], ['version', 'asc']).map(pt=>{ + const param = _.omit(pt, ['id', 'version', 'revision', 'key']); + return axios + .post(destUrl + `metadata/form/${pt.key}/versions`, param, {headers:headers}) + .catch((err) => { + const errMessage = _.get(err, 'response.data.message', ''); + console.log(`Failed to create form with key=${pt.key} version=${pt.version}.`, errMessage) + }) + }); + + await Promise.all(promises); + + promises = _(data.result.content.planConfigs).orderBy(['key', 'asc'], ['version', 'asc']).map(pt=>{ + const param = _.omit(pt, ['id', 'version', 'revision', 'key']); + return axios + .post(destUrl + `metadata/planConfig/${pt.key}/versions`, param, {headers:headers}) + .catch((err) => { + const errMessage = _.get(err, 'response.data.message', ''); + console.log(`Failed to create planConfig with key=${pt.key} version=${pt.version}.`, errMessage) + }) + }); + + await Promise.all(promises); + + promises = _(data.result.content.priceConfigs).orderBy(['key', 'asc'], ['version', 'asc']).map(pt=>{ + const param = _.omit(pt, ['id', 'version', 'revision', 'key']); + return axios + .post(destUrl + `metadata/priceConfig/${pt.key}/versions`, param, {headers:headers}) + .catch((err) => { + const errMessage = _.get(err, 'response.data.message', ''); + console.log(`Failed to create priceConfig with key=${pt.key} version=${pt.version}.`, errMessage) + }) + }); + + await Promise.all(promises); + + promises = _(data.result.content.projectTypes).map(pt=>{ return axios .post(destUrl+'metadata/projectTypes', pt, {headers:headers}) .catch((err) => { diff --git a/local/seed/seedProjects.js b/local/seed/seedProjects.js index 94677587..b8538b53 100644 --- a/local/seed/seedProjects.js +++ b/local/seed/seedProjects.js @@ -60,6 +60,21 @@ module.exports = (targetUrl, token) => { }); } + await models.ProjectEstimation.create({ + projectId, + buildingBlockKey: 'BLOCK_KEY', + conditions: '( HAS_DEV_DELIVERABLE && ONLY_ONE_OS_MOBILE && CA_NEEDED )', + price: 6500.50, + quantity: 10, + minTime: 35, + maxTime: 35, + metadata: { + deliverable: 'dev-qa', + }, + createdBy: 1, + updatedBy: 1, + }); + // creating invitations if (Array.isArray(invites)) { let promises = [] diff --git a/src/models/form.js b/src/models/form.js index 1c41b53c..bf7ebe37 100644 --- a/src/models/form.js +++ b/src/models/form.js @@ -42,6 +42,7 @@ module.exports = (sequelize, DataTypes) => { Form.latestVersion = classMethods.latestVersion; Form.latestRevisionOfLatestVersion = classMethods.latestRevisionOfLatestVersion; Form.latestVersionIncludeUsed = classMethods.latestVersionIncludeUsed; + Form.findOneWithLatestRevision = classMethods.findOneWithLatestRevision; return Form; }; diff --git a/src/models/projectMemberInvite.js b/src/models/projectMemberInvite.js index f45d4b78..bacee6cd 100644 --- a/src/models/projectMemberInvite.js +++ b/src/models/projectMemberInvite.js @@ -67,7 +67,10 @@ module.exports = function defineProjectMemberInvite(sequelize, DataTypes) { const where = { projectId, status: INVITE_STATUS.PENDING }; if (email && userId) { - _.assign(where, { $or: [{ email: { $eq: email } }, { userId: { $eq: userId } }] }); + _.assign(where, { $or: [ + { email: { $eq: email.toLowerCase() } }, + { userId: { $eq: userId } }, + ] }); } else if (email) { _.assign(where, { email }); } else if (userId) { diff --git a/src/models/projectTemplate.js b/src/models/projectTemplate.js index dd61765e..2d14299c 100644 --- a/src/models/projectTemplate.js +++ b/src/models/projectTemplate.js @@ -1,4 +1,7 @@ /* eslint-disable valid-jsdoc */ +import _ from 'lodash'; + +import models from './'; /** * The Project Template model @@ -35,5 +38,15 @@ module.exports = (sequelize, DataTypes) => { deletedAt: 'deletedAt', }); + ProjectTemplate.getTemplate = templateId => + ProjectTemplate.findByPk(templateId, { raw: true }) + .then((template) => { + const formRef = template.form; + return formRef + ? models.Form.findAll({ where: formRef, raw: true }) + .then(forms => Object.assign({}, template, { form: _.maxBy(forms, f => f.revision) })) + : template; + }); + return ProjectTemplate; }; diff --git a/src/routes/admin/project-index-create.js b/src/routes/admin/project-index-create.js index 9a421516..a6f8dfb8f9 100644 --- a/src/routes/admin/project-index-create.js +++ b/src/routes/admin/project-index-create.js @@ -117,6 +117,7 @@ module.exports = [ }) .then((result) => { logger.debug(`project indexed successfully (projectId: ${projectIdStart}-${projectIdEnd})`, result); + logger.debug(result); }) .catch((error) => { logger.error(`Error in indexing project (projectId: ${projectIdStart}-${projectIdEnd})`, error); diff --git a/src/routes/form/version/getVersion.js b/src/routes/form/version/getVersion.js index b8a49431..950e87b6 100644 --- a/src/routes/form/version/getVersion.js +++ b/src/routes/form/version/getVersion.js @@ -46,15 +46,7 @@ module.exports = [ .then((data) => { if (data.length === 0) { req.log.debug('No form found in ES'); - models.Form.findOne({ - where: { - key: req.params.key, - version: req.params.version, - }, - order: [['revision', 'DESC']], - limit: 1, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - }) + models.Form.findOneWithLatestRevision(req.params) .then((form) => { // Not found if (!form) { diff --git a/src/routes/milestones/delete.js b/src/routes/milestones/delete.js index 620b24ed..ade643c7 100644 --- a/src/routes/milestones/delete.js +++ b/src/routes/milestones/delete.js @@ -46,7 +46,8 @@ module.exports = [ // Update the deletedBy, and soft delete return milestone.update({ deletedBy: req.authUser.userId }) .then(() => milestone.destroy()); - }) + }), + ) .then((deleted) => { // Send event to bus req.log.debug('Sending event to RabbitMQ bus for milestone %d', deleted.id); @@ -66,6 +67,6 @@ module.exports = [ res.status(204).end(); return Promise.resolve(); }) - .catch(next)); + .catch(next); }, ]; diff --git a/src/routes/milestones/delete.spec.js b/src/routes/milestones/delete.spec.js index edbc2fb7..798ef241 100644 --- a/src/routes/milestones/delete.spec.js +++ b/src/routes/milestones/delete.spec.js @@ -16,7 +16,6 @@ const should = chai.should(); // eslint-disable-line no-unused-vars const expectAfterDelete = (timelineId, id, err, next) => { if (err) throw err; - setTimeout(() => models.Milestone.findOne({ where: { timelineId, @@ -30,9 +29,15 @@ const expectAfterDelete = (timelineId, id, err, next) => { } else { chai.assert.isNotNull(res.deletedAt); chai.assert.isNotNull(res.deletedBy); + + request(server) + .get(`/v5/timelines/${timelineId}/milestones/${id}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(404, next); } - next(); - }), 500); + }); }; describe('DELETE milestone', () => { diff --git a/src/routes/milestones/get.spec.js b/src/routes/milestones/get.spec.js index 1c6797da..4b563847 100644 --- a/src/routes/milestones/get.spec.js +++ b/src/routes/milestones/get.spec.js @@ -303,6 +303,15 @@ describe('GET milestone', () => { should.not.exist(resJson.deletedBy); should.not.exist(resJson.deletedAt); + // validate statusHistory + should.exist(resJson.statusHistory); + resJson.statusHistory.should.be.an('array'); + resJson.statusHistory.length.should.be.eql(1); + resJson.statusHistory.forEach((statusHistory) => { + statusHistory.reference.should.be.eql('milestone'); + statusHistory.referenceId.should.be.eql(resJson.id); + }); + done(); }); }); diff --git a/src/routes/productTemplates/list.js b/src/routes/productTemplates/list.js index 6817fb56..3dab4edd 100644 --- a/src/routes/productTemplates/list.js +++ b/src/routes/productTemplates/list.js @@ -10,31 +10,22 @@ const permissions = tcMiddleware.permissions; module.exports = [ permissions('productTemplate.view'), (req, res, next) => { - util.fetchFromES('productTemplates') - .then((data) => { - const filters = req.query; - if (!util.isValidFilter(filters, ['productKey'])) { - util.handleError('Invalid filters', null, req, next); - } - const where = { deletedAt: { $eq: null } }; - if (filters.productKey) { - where.productKey = { $eq: filters.productKey }; - } - if (data.productTemplates.length === 0) { - req.log.debug('No productTemplate found in ES'); - models.ProductTemplate.findAll({ - where, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((productTemplates) => { - res.json(productTemplates); - }) - .catch(next); - } else { - req.log.debug('productTemplates found in ES'); - res.json(data.productTemplates); - } - }); + const filters = util.parseQueryFilter(req.query.filter); + if (!util.isValidFilter(filters, ['productKey'])) { + return util.handleError('Invalid filters', null, req, next); + } + const where = { deletedAt: { $eq: null }, disabled: false }; + if (filters.productKey) { + where.productKey = { $eq: filters.productKey }; + } + return models.ProductTemplate.findAll({ + where, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((productTemplates) => { + res.json(productTemplates); + }) + .catch(next); }, ]; diff --git a/src/routes/productTemplates/list.spec.js b/src/routes/productTemplates/list.spec.js index 50d6ff7b..37e21666 100644 --- a/src/routes/productTemplates/list.spec.js +++ b/src/routes/productTemplates/list.spec.js @@ -9,11 +9,11 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; -const should = chai.should(); +const should = chai.should(); // eslint-disable-line no-unused-vars const validateProductTemplates = (count, resJson, expectedTemplates) => { - should.exist(resJson); resJson.length.should.be.eql(count); + resJson.forEach((pt, idx) => { pt.should.include.all.keys('id', 'name', 'productKey', 'category', 'subCategory', 'icon', 'brief', 'details', 'aliases', 'template', 'disabled', 'form', 'hidden', 'isAddOn', 'createdBy', 'createdAt', 'updatedBy', 'updatedAt'); @@ -52,7 +52,7 @@ describe('LIST product templates', () => { }, alias2: [1, 2, 3], }, - disabled: true, + disabled: false, hidden: true, isAddOn: true, template: { @@ -192,7 +192,7 @@ describe('LIST product templates', () => { .expect(200) .end((err, res) => { const resJson = res.body; - validateProductTemplates(1, resJson, [templates[1]]); + validateProductTemplates(1, [resJson[1]], [templates[1]]); done(); }); }); diff --git a/src/routes/projectTemplates/list.js b/src/routes/projectTemplates/list.js index f278c2ef..b89ff43a 100644 --- a/src/routes/projectTemplates/list.js +++ b/src/routes/projectTemplates/list.js @@ -3,31 +3,21 @@ */ import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; -import util from '../../util'; const permissions = tcMiddleware.permissions; module.exports = [ permissions('projectTemplate.view'), - (req, res, next) => { - util.fetchFromES('projectTemplates') - .then((data) => { - if (data.projectTemplates.length === 0) { - req.log.debug('No projectTemplate found in ES'); - models.ProjectTemplate.findAll({ - where: { - deletedAt: { $eq: null }, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }).then((projectTemplates) => { - res.json(projectTemplates); - }) - .catch(next); - } else { - req.log.debug('projectTemplates found in ES'); - res.json(data.projectTemplates); - } - }); - }, + (req, res, next) => models.ProjectTemplate.findAll({ + where: { + deletedAt: { $eq: null }, + disabled: false, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((projectTemplates) => { + res.json(projectTemplates); + }) + .catch(next), ]; diff --git a/src/routes/projectTemplates/list.spec.js b/src/routes/projectTemplates/list.spec.js index 02a6a4b4..25eaae95 100644 --- a/src/routes/projectTemplates/list.spec.js +++ b/src/routes/projectTemplates/list.spec.js @@ -20,7 +20,7 @@ describe('LIST project templates', () => { question: 'question 1', info: 'info 1', aliases: ['key-1', 'key_1'], - disabled: true, + disabled: false, hidden: true, scope: { scope1: { diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index e06adf8d..5ecab5c7 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -247,7 +247,8 @@ describe('LIST Project', () => { }).then(() => { // sleep for some time, let elasticsearch indices be settled // sleep.sleep(5); - done(); + testUtil.wait(done); + // done(); }); }); }); diff --git a/src/routes/timelines/create.js b/src/routes/timelines/create.js index cb2e09d0..fbc361c2 100644 --- a/src/routes/timelines/create.js +++ b/src/routes/timelines/create.js @@ -6,10 +6,9 @@ import _ from 'lodash'; import Joi from 'joi'; import moment from 'moment'; import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; import validateTimeline from '../../middlewares/validateTimeline'; import models from '../../models'; -import { EVENT, RESOURCES, TIMELINE_REFERENCES, MILESTONE_STATUS, MILESTONE_TEMPLATE_REFERENCES } +import { EVENT, TIMELINE_REFERENCES, MILESTONE_STATUS, MILESTONE_TEMPLATE_REFERENCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -49,7 +48,7 @@ module.exports = [ let result; // Save to DB - models.sequelize.transaction(() => { + return models.sequelize.transaction(() => { req.log.debug('Started transaction'); return models.Timeline.create(entity) .then((createdEntity) => { @@ -107,8 +106,7 @@ module.exports = [ }); } return Promise.resolve(); - }) - .catch(next); + }); }) .then(() => { // Send event to bus @@ -118,19 +116,6 @@ module.exports = [ { correlationId: req.id }, ); - // emit the event - util.sendResourceToKafkaBus( - req, - EVENT.ROUTING_KEY.TIMELINE_ADDED, - RESOURCES.TIMELINE, - result); - - // emit the event for milestones - _.map(result.milestones, milestone => util.sendResourceToKafkaBus(req, - EVENT.ROUTING_KEY.MILESTONE_ADDED, - RESOURCES.MILESTONE, - milestone)); - // Write to the response res.status(201).json(result); return Promise.resolve(); diff --git a/src/routes/timelines/create.spec.js b/src/routes/timelines/create.spec.js index 6879987f..8e94687e 100644 --- a/src/routes/timelines/create.spec.js +++ b/src/routes/timelines/create.spec.js @@ -505,6 +505,15 @@ describe('CREATE timeline', () => { should.exist(milestone.updatedAt); should.not.exist(milestone.deletedBy); should.not.exist(milestone.deletedAt); + + // validate statusHistory + should.exist(milestone.statusHistory); + milestone.statusHistory.should.be.an('array'); + milestone.statusHistory.length.should.be.eql(1); + milestone.statusHistory.forEach((statusHistory) => { + statusHistory.reference.should.be.eql('milestone'); + statusHistory.referenceId.should.be.eql(milestone.id); + }); }); // eslint-disable-next-line no-unused-expressions diff --git a/src/routes/timelines/delete.spec.js b/src/routes/timelines/delete.spec.js index e9f6d1c3..e082578c 100644 --- a/src/routes/timelines/delete.spec.js +++ b/src/routes/timelines/delete.spec.js @@ -159,6 +159,7 @@ describe('DELETE timeline', () => { // Create milestones models.Milestone.bulkCreate([ { + id: 1, timelineId: 1, name: 'milestone 1', duration: 2, @@ -181,6 +182,7 @@ describe('DELETE timeline', () => { updatedBy: 2, }, { + id: 2, timelineId: 1, name: 'milestone 2', duration: 2, diff --git a/src/routes/timelines/get.spec.js b/src/routes/timelines/get.spec.js index 7bec2f82..01ac60f5 100644 --- a/src/routes/timelines/get.spec.js +++ b/src/routes/timelines/get.spec.js @@ -264,6 +264,16 @@ describe('GET timeline', () => { // Milestones resJson.milestones.should.have.length(2); + resJson.milestones.forEach((milestone) => { + // validate statusHistory + should.exist(milestone.statusHistory); + milestone.statusHistory.should.be.an('array'); + milestone.statusHistory.length.should.be.eql(1); + milestone.statusHistory.forEach((statusHistory) => { + statusHistory.reference.should.be.eql('milestone'); + statusHistory.referenceId.should.be.eql(milestone.id); + }); + }); done(); }); diff --git a/src/routes/timelines/update.spec.js b/src/routes/timelines/update.spec.js index 1d08ccc5..21b4dac3 100644 --- a/src/routes/timelines/update.spec.js +++ b/src/routes/timelines/update.spec.js @@ -470,6 +470,19 @@ describe('UPDATE timeline', () => { should.not.exist(resJson.deletedAt); should.not.exist(resJson.deletedBy); + // Milestones + resJson.milestones.should.have.length(2); + resJson.milestones.forEach((milestone) => { + // validate statusHistory + should.exist(milestone.statusHistory); + milestone.statusHistory.should.be.an('array'); + milestone.statusHistory.length.should.be.eql(1); + milestone.statusHistory.forEach((statusHistory) => { + statusHistory.reference.should.be.eql('milestone'); + statusHistory.referenceId.should.be.eql(milestone.id); + }); + }); + // eslint-disable-next-line no-unused-expressions server.services.pubsub.publish.calledWith(EVENT.ROUTING_KEY.TIMELINE_UPDATED).should.be.true; @@ -637,6 +650,8 @@ describe('UPDATE timeline', () => { createEventSpy.calledOnce.should.be.true; createEventSpy.calledWith(BUS_API_EVENT.TIMELINE_UPDATED, sinon.match({ resource: RESOURCES.TIMELINE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.TIMELINE_UPDATED, + sinon.match({ name: body.name })).should.be.true; done(); }); } diff --git a/src/tests/serviceMocks.js b/src/tests/serviceMocks.js index 662bd2c4..060596df 100644 --- a/src/tests/serviceMocks.js +++ b/src/tests/serviceMocks.js @@ -7,16 +7,13 @@ import _ from 'lodash'; import config from 'config'; import elasticsearch from 'elasticsearch'; import util from '../util'; +import mockRabbitMQ from './mockRabbitMQ'; module.exports = (app) => { + mockRabbitMQ(app); + _.assign(app.services, { - pubsub: { - init: () => {}, - publish: () => {}, - }, es: new elasticsearch.Client(_.cloneDeep(config.elasticsearchConfig)), }); - sinon.stub(app.services.pubsub, 'init', () => Promise.resolve(true)); - sinon.stub(app.services.pubsub, 'publish', () => Promise.resolve(true)); sinon.stub(util, 'getM2MToken', () => Promise.resolve('MOCK_TOKEN')); }; diff --git a/src/tests/util.js b/src/tests/util.js index 3031dd1b..2996f4c9 100644 --- a/src/tests/util.js +++ b/src/tests/util.js @@ -26,6 +26,8 @@ export default { member2: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJtZW1iZXIyIiwiZXhwIjoyNTYzMDc2Njg5LCJ1c2VySWQiOiI0MDA1MTMzNSIsImlhdCI6MTQ2MzA3NjA4OSwiZW1haWwiOiJ0ZXN0QHRvcGNvZGVyLmNvbSIsImp0aSI6ImIzM2I3N2NkLWI1MmUtNDBmZS04MzdlLWJlYjhlMGFlNmE0YSJ9.Mh4bw3wm-cn5Kcf96gLFVlD0kySOqqk4xN3qnreAKL4', // userId = 40051336, [ 'Connect Admin' ], handle: 'connect_admin1', email: 'connect_admin1@topcoder.com' connectAdmin: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJDb25uZWN0IEFkbWluIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJjb25uZWN0X2FkbWluMSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzYiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoiY29ubmVjdF9hZG1pbjFAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.nSGfXMl02NZ90ZKLiEKPg75iAjU92mfteaY6xgqkM30', + // userId = 40158431, [ 'Topcoder user' ], handle: 'romitchoudhary', email: 'romit.choudhary@rivigo.com' + romit: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJyb21pdGNob3VkaGFyeSIsImV4cCI6MTU2MjkxOTc5MSwidXNlcklkIjoiNDAxNTg0MzEiLCJpYXQiOjE1NjI5MTkxOTEsImVtYWlsIjoicm9taXQuY2hvdWRoYXJ5QHJpdmlnby5jb20iLCJqdGkiOiJlMmM1ZTc2NS03OTI5LTRiNzgtYjI2OS1iZDRlODA0NDI4YjMifQ.P1CoydCJuQ8Hv_b0-a8V7Wu0pgIt9qv4NYyB7FTbua0', }, userIds: { member: 40051331, @@ -34,6 +36,7 @@ export default { manager: 40051334, member2: 40051335, connectAdmin: 40051336, + romit: 40158431, }, getDecodedToken: token => jwt.decode(token), diff --git a/src/util.spec.js b/src/util.spec.js new file mode 100644 index 00000000..99722697 --- /dev/null +++ b/src/util.spec.js @@ -0,0 +1,39 @@ +/** + * Tests for util.js + */ +import chai from 'chai'; +import util from './util'; + +chai.should(); + +describe('Util method', () => { + describe('isProjectSettingForEstimation', () => { + it('should return "true" if key is correct: "markup_fee"', () => { + util.isProjectSettingForEstimation('markup_fee').should.equal(true); + }); + + it('should return "false" if key has unknown estimation type: "markup_unknown"', () => { + util.isProjectSettingForEstimation('markup_unknown').should.equal(false); + }); + + it('should return "false" if key doesn\'t have "markup_" prefix: "fee"', () => { + util.isProjectSettingForEstimation('fee').should.equal(false); + }); + + it('should return "false" if key doesn\'t have duplicated prefix "markup_": "markup_markup_fee"', () => { + util.isProjectSettingForEstimation('markup_markup_fee').should.equal(false); + }); + + it('should return "false" if has prefix "markup_" at the end: "feemarkup_"', () => { + util.isProjectSettingForEstimation('feemarkup_').should.equal(false); + }); + + it('should return "false" if has additional text after: "markup_fee_text"', () => { + util.isProjectSettingForEstimation('markup_fee_text').should.equal(false); + }); + + it('should return "false" if has additional text before: "text_markup_fee"', () => { + util.isProjectSettingForEstimation('text_markup_fee').should.equal(false); + }); + }); +}); From c6abfb266c5b36a457443a6d896cb3cba31aa03a Mon Sep 17 00:00:00 2001 From: gets0ul Date: Fri, 18 Oct 2019 00:08:08 +0700 Subject: [PATCH 08/88] Remove duplicates. --- src/events/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/events/index.js b/src/events/index.js index 67b1b151..f5eee2e4 100644 --- a/src/events/index.js +++ b/src/events/index.js @@ -41,9 +41,6 @@ export const rabbitHandlers = { [EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED]: projectPhaseAddedHandler, [EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED]: projectPhaseRemovedHandler, [EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED]: projectPhaseUpdatedHandler, - [EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED]: projectPhaseAddedHandler, - [EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED]: projectPhaseRemovedHandler, - [EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED]: projectPhaseUpdatedHandler, [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED]: phaseProductAddedHandler, [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED]: phaseProductRemovedHandler, [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED]: phaseProductUpdatedHandler, From c5c36a542f7fbd6cbb23ebc5e21fc24b414728ae Mon Sep 17 00:00:00 2001 From: gets0ul Date: Sat, 19 Oct 2019 01:13:14 +0700 Subject: [PATCH 09/88] More fixes based on feedback. --- config/custom-environment-variables.json | 13 +- config/default.json | 13 +- local/seed/seedProjects.js | 1 + package.json | 4 +- src/models/projectPhase.js | 2 + src/routes/form/version/getVersion.js | 8 +- src/routes/metadata/list.spec.js | 2 +- src/routes/milestones/create.js | 2 +- src/routes/milestones/create.spec.js | 18 +- src/routes/milestones/delete.spec.js | 3 + src/routes/milestones/get.spec.js | 3 + src/routes/milestones/update.spec.js | 28 +- src/routes/phaseProducts/create.spec.js | 66 ++- src/routes/phaseProducts/delete.spec.js | 60 ++- src/routes/phaseProducts/update.spec.js | 66 ++- src/routes/phases/create.spec.js | 220 ++++++++-- src/routes/phases/delete.spec.js | 172 +++++++- src/routes/phases/update.spec.js | 408 +++++++++++++++++-- src/routes/planConfig/version/getVersion.js | 8 +- src/routes/priceConfig/version/getVersion.js | 8 +- src/routes/productTemplates/list.js | 43 +- src/routes/productTemplates/list.spec.js | 4 +- 22 files changed, 1023 insertions(+), 129 deletions(-) diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index a44ecae3..733bbbc6 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -53,5 +53,16 @@ "inviteEmailSubject": "INVITE_EMAIL_SUBJECT", "inviteEmailSectionTitle": "INVITE_EMAIL_SECTION_TITLE", "pageSize": "PAGE_SIZE", - "SSO_REFCODES": "SSO_REFCODES" + "SSO_REFCODES": "SSO_REFCODES", + "lookerConfig": { + "BASE_URL": "LOOKER_API_BASE_URL", + "CLIENT_ID": "LOOKER_API_CLIENT_ID", + "CLIENT_SECRET": "LOOKER_API_CLIENT_SECRET", + "TOKEN": "TOKEN", + "USE_MOCK": "LOOKER_API_ENABLE_MOCK", + "QUERIES": { + "REG_STATS": "LOOKER_API_REG_STATS_QUERY_ID", + "BUDGET": "LOOKER_API_BUDGET_QUERY_ID" + } + } } diff --git a/config/default.json b/config/default.json index 8ea02f6a..83446f6d 100644 --- a/config/default.json +++ b/config/default.json @@ -58,5 +58,16 @@ "UNIQUE_GMAIL_VALIDATION": false, "pageSize": 20, "VALID_STATUSES_BEFORE_PAUSED": "[\"active\"]", - "SSO_REFCODES": "[]" + "SSO_REFCODES": "[]", + "lookerConfig": { + "BASE_URL": "", + "CLIENT_ID": "", + "CLIENT_SECRET": "", + "TOKEN": "TOKEN", + "USE_MOCK": "true", + "QUERIES": { + "REG_STATS": 1234, + "BUDGET": 123 + } + } } diff --git a/local/seed/seedProjects.js b/local/seed/seedProjects.js index b8538b53..a84b82d8 100644 --- a/local/seed/seedProjects.js +++ b/local/seed/seedProjects.js @@ -1,4 +1,5 @@ import util from '../../src/tests/util'; +import models from '../../src/models'; const axios = require('axios'); const Promise = require('bluebird'); diff --git a/package.json b/package.json index 241eb814..5d4dca21 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "lint": "./node_modules/.bin/eslint .", "lint:fix": "./node_modules/.bin/eslint . --fix || true", - "build": "babel src -d dist --presets es2015", + "build": "babel src -d dist --presets es2015 --copy-files", "sync:db": "./node_modules/.bin/babel-node migrations/sync.js", "sync:es": "./node_modules/.bin/babel-node migrations/elasticsearch_sync.js", "migrate:es": "./node_modules/.bin/babel-node migrations/seedElasticsearchIndex.js", @@ -75,7 +75,7 @@ "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-es2015": "^6.9.0", - "bunyan": "^1.8.1", + "bunyan": "^1.8.12", "chai": "^3.5.0", "chai-as-promised": "^7.1.1", "eslint": "^3.16.1", diff --git a/src/models/projectPhase.js b/src/models/projectPhase.js index ef6e2920..5c260e1d 100644 --- a/src/models/projectPhase.js +++ b/src/models/projectPhase.js @@ -4,6 +4,8 @@ module.exports = function defineProjectPhase(sequelize, DataTypes) { const ProjectPhase = sequelize.define('ProjectPhase', { id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true }, name: { type: DataTypes.STRING, allowNull: true }, + description: { type: DataTypes.STRING, allowNull: true }, + requirements: { type: DataTypes.STRING, allowNull: true }, status: { type: DataTypes.STRING, allowNull: true }, startDate: { type: DataTypes.DATE, allowNull: true }, endDate: { type: DataTypes.DATE, allowNull: true }, diff --git a/src/routes/form/version/getVersion.js b/src/routes/form/version/getVersion.js index 950e87b6..d4167150 100644 --- a/src/routes/form/version/getVersion.js +++ b/src/routes/form/version/getVersion.js @@ -46,7 +46,7 @@ module.exports = [ .then((data) => { if (data.length === 0) { req.log.debug('No form found in ES'); - models.Form.findOneWithLatestRevision(req.params) + return models.Form.findOneWithLatestRevision(req.params) .then((form) => { // Not found if (!form) { @@ -58,10 +58,10 @@ module.exports = [ return Promise.resolve(); }) .catch(next); - } else { - req.log.debug('forms found in ES'); - res.json(data[0].inner_hits.forms.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle } + req.log.debug('forms found in ES'); + res.json(data[0].inner_hits.forms.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + return Promise.resolve(); }) .catch(next); }, diff --git a/src/routes/metadata/list.spec.js b/src/routes/metadata/list.spec.js index f9ae92ec..52956d08 100644 --- a/src/routes/metadata/list.spec.js +++ b/src/routes/metadata/list.spec.js @@ -112,7 +112,7 @@ const forms = [ { key: 'productKey 1', config: { - questions: [{ + sections: [{ id: 'appDefinition', title: 'Sample Project', required: true, diff --git a/src/routes/milestones/create.js b/src/routes/milestones/create.js index b297411b..bf502dd8 100644 --- a/src/routes/milestones/create.js +++ b/src/routes/milestones/create.js @@ -58,7 +58,7 @@ module.exports = [ }); let result; - // Validate startDate and endDate to be within the timeline startDate and endDate + // Validate startDate is not earlier than timeline startDate let error; if (req.body.startDate < req.timeline.startDate) { error = 'Milestone startDate must not be before the timeline startDate'; diff --git a/src/routes/milestones/create.spec.js b/src/routes/milestones/create.spec.js index bcd0e7f2..6c28436d 100644 --- a/src/routes/milestones/create.spec.js +++ b/src/routes/milestones/create.spec.js @@ -142,6 +142,7 @@ describe('CREATE milestone', () => { // Create milestones models.Milestone.bulkCreate([ { + id: 11, timelineId: 1, name: 'milestone 1', duration: 2, @@ -164,6 +165,7 @@ describe('CREATE milestone', () => { updatedBy: 2, }, { + id: 12, timelineId: 1, name: 'milestone 2', duration: 3, @@ -179,6 +181,7 @@ describe('CREATE milestone', () => { updatedBy: 3, }, { + id: 13, timelineId: 1, name: 'milestone 3', duration: 4, @@ -424,6 +427,15 @@ describe('CREATE milestone', () => { should.not.exist(resJson.deletedBy); should.not.exist(resJson.deletedAt); + // validate statusHistory + should.exist(resJson.statusHistory); + resJson.statusHistory.should.be.an('array'); + resJson.statusHistory.length.should.be.eql(1); + resJson.statusHistory.forEach((statusHistory) => { + statusHistory.reference.should.be.eql('milestone'); + statusHistory.referenceId.should.be.eql(resJson.id); + }); + // eslint-disable-next-line no-unused-expressions server.services.pubsub.publish.calledWith(EVENT.ROUTING_KEY.MILESTONE_ADDED).should.be.true; @@ -431,11 +443,11 @@ describe('CREATE milestone', () => { models.Milestone.findAll({ where: { timelineId: 1 } }) .then((milestones) => { _.each(milestones, (milestone) => { - if (milestone.id === 1) { + if (milestone.id === 11) { milestone.order.should.be.eql(1); - } else if (milestone.id === 2) { + } else if (milestone.id === 12) { milestone.order.should.be.eql(2 + 1); - } else if (milestone.id === 3) { + } else if (milestone.id === 13) { milestone.order.should.be.eql(3 + 1); } }); diff --git a/src/routes/milestones/delete.spec.js b/src/routes/milestones/delete.spec.js index 798ef241..d1f4ada2 100644 --- a/src/routes/milestones/delete.spec.js +++ b/src/routes/milestones/delete.spec.js @@ -162,6 +162,7 @@ describe('DELETE milestone', () => { // Create milestones models.Milestone.bulkCreate([ { + id: 1, timelineId: 1, name: 'milestone 1', duration: 2, @@ -184,6 +185,7 @@ describe('DELETE milestone', () => { updatedBy: 2, }, { + id: 2, timelineId: 1, name: 'milestone 2', duration: 3, @@ -199,6 +201,7 @@ describe('DELETE milestone', () => { updatedBy: 3, }, { + id: 3, timelineId: 1, name: 'milestone 3', duration: 4, diff --git a/src/routes/milestones/get.spec.js b/src/routes/milestones/get.spec.js index 4b563847..f541eb02 100644 --- a/src/routes/milestones/get.spec.js +++ b/src/routes/milestones/get.spec.js @@ -133,6 +133,7 @@ describe('GET milestone', () => { // Create milestones models.Milestone.bulkCreate([ { + id: 1, timelineId: 1, name: 'milestone 1', duration: 2, @@ -155,6 +156,7 @@ describe('GET milestone', () => { updatedBy: 2, }, { + id: 2, timelineId: 1, name: 'milestone 2', duration: 3, @@ -170,6 +172,7 @@ describe('GET milestone', () => { updatedBy: 3, }, { + id: 3, timelineId: 1, name: 'milestone 3', duration: 4, diff --git a/src/routes/milestones/update.spec.js b/src/routes/milestones/update.spec.js index 74b5a36e..c41c0431 100644 --- a/src/routes/milestones/update.spec.js +++ b/src/routes/milestones/update.spec.js @@ -265,7 +265,6 @@ describe('UPDATE Milestone', () => { const body = { name: 'Milestone 1-updated', duration: 3, - completionDate: '2018-05-16T00:00:00.000Z', description: 'description-updated', status: 'draft', type: 'type1-updated', @@ -512,12 +511,14 @@ describe('UPDATE Milestone', () => { }); it('should return 200 for admin', (done) => { + const newBody = _.cloneDeep(body); + newBody.completionDate = '2018-05-15T00:00:00.000Z'; request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) - .send(body) + .send(newBody) .expect(200) .end((err, res) => { const resJson = res.body; @@ -525,7 +526,7 @@ describe('UPDATE Milestone', () => { resJson.name.should.be.eql(body.name); resJson.description.should.be.eql(body.description); resJson.duration.should.be.eql(body.duration); - resJson.completionDate.should.be.eql(body.completionDate); + resJson.completionDate.should.be.eql(newBody.completionDate); resJson.status.should.be.eql(body.status); resJson.type.should.be.eql(body.type); resJson.details.should.be.eql({ @@ -546,6 +547,15 @@ describe('UPDATE Milestone', () => { should.not.exist(resJson.deletedBy); should.not.exist(resJson.deletedAt); + // validate statusHistory + should.exist(resJson.statusHistory); + resJson.statusHistory.should.be.an('array'); + resJson.statusHistory.length.should.be.eql(2); + resJson.statusHistory.forEach((statusHistory) => { + statusHistory.reference.should.be.eql('milestone'); + statusHistory.referenceId.should.be.eql(resJson.id); + }); + // eslint-disable-next-line no-unused-expressions server.services.pubsub.publish.calledWith(EVENT.ROUTING_KEY.MILESTONE_UPDATED).should.be.true; @@ -1098,36 +1108,36 @@ describe('UPDATE Milestone', () => { .expect(200, done); }); - it('should return 403 for connect manager when entity to update has completionDate', (done) => { + it('should return 200 for connect manager', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.manager}`, }) .send(body) - .expect(403) + .expect(200) .end(done); }); - it('should return 403 for copilot when entity to update has completionDate', (done) => { + it('should return 200 for copilot', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send(body) - .expect(403) + .expect(200) .end(done); }); - it('should return 403 for member when entity to update has completionDate', (done) => { + it('should return 200 for member', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.member}`, }) .send(body) - .expect(403) + .expect(200) .end(done); }); diff --git a/src/routes/phaseProducts/create.spec.js b/src/routes/phaseProducts/create.spec.js index e967997f..b4f104e7 100644 --- a/src/routes/phaseProducts/create.spec.js +++ b/src/routes/phaseProducts/create.spec.js @@ -177,7 +177,7 @@ describe('Phase Products', () => { request(server) .post(`/v5/projects/99999/phases/${phaseId}/products`) .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send(body) .expect('Content-Type', /json/) @@ -199,7 +199,7 @@ describe('Phase Products', () => { request(server) .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send(body) .expect('Content-Type', /json/) @@ -220,6 +220,68 @@ describe('Phase Products', () => { }); }); + it('should return 201 if requested by admin', (done) => { + request(server) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end(done); + }); + + it('should return 201 if requested by manager which is a member', (done) => { + models.ProjectMember.create({ + id: 3, + userId: testUtil.userIds.manager, + projectId, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }).then(() => { + request(server) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end(done); + }); + }); + + it('should return 403 if requested by manager which is not a member', (done) => { + request(server) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(403) + .end(done); + }); + + it('should return 403 if requested by non-member copilot', (done) => { + models.ProjectMember.destroy({ + where: { userId: testUtil.userIds.copilot, projectId }, + }).then(() => { + request(server) + .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(403) + .end(done); + }); + }); + describe('Bus api', () => { let createEventSpy; const sandbox = sinon.sandbox.create(); diff --git a/src/routes/phaseProducts/delete.spec.js b/src/routes/phaseProducts/delete.spec.js index 76b609fa..174c03c1 100644 --- a/src/routes/phaseProducts/delete.spec.js +++ b/src/routes/phaseProducts/delete.spec.js @@ -152,7 +152,7 @@ describe('Phase Products', () => { request(server) .delete(`/v5/projects/999/phases/${phaseId}/products/${productId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .expect('Content-Type', /json/) .expect(404, done); @@ -162,7 +162,7 @@ describe('Phase Products', () => { request(server) .delete(`/v5/projects/${projectId}/phases/99999/products/${productId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .expect('Content-Type', /json/) .expect(404, done); @@ -172,7 +172,7 @@ describe('Phase Products', () => { request(server) .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/99999`) .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .expect('Content-Type', /json/) .expect(404, done); @@ -188,6 +188,60 @@ describe('Phase Products', () => { .end(err => expectAfterDelete(projectId, phaseId, productId, err, done)); }); + it('should return 204 if requested by admin', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(204) + .end(done); + }); + + it('should return 204 if requested by manager which is a member', (done) => { + models.ProjectMember.create({ + id: 3, + userId: testUtil.userIds.manager, + projectId, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }).then(() => { + request(server) + .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(204) + .end(done); + }); + }); + + it('should return 403 if requested by manager which is not a member', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(403) + .end(done); + }); + + it('should return 403 if requested by non-member copilot', (done) => { + models.ProjectMember.destroy({ + where: { userId: testUtil.userIds.copilot, projectId }, + }).then(() => { + request(server) + .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403) + .end(done); + }); + }); + describe('Bus api', () => { let createEventSpy; const sandbox = sinon.sandbox.create(); diff --git a/src/routes/phaseProducts/update.spec.js b/src/routes/phaseProducts/update.spec.js index 3fc58dbe..b23a5672 100644 --- a/src/routes/phaseProducts/update.spec.js +++ b/src/routes/phaseProducts/update.spec.js @@ -51,7 +51,7 @@ describe('Phase Products', () => { lastName: 'lName', email: 'some@abc.com', }; - before((done) => { + beforeEach((done) => { // mocks testUtil.clearDb() .then(() => { @@ -144,7 +144,7 @@ describe('Phase Products', () => { request(server) .patch(`/v5/projects/999/phases/${phaseId}/products/${productId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send(updateBody) .expect('Content-Type', /json/) @@ -212,6 +212,68 @@ describe('Phase Products', () => { }); }); + it('should return 200 if requested by admin', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(200) + .end(done); + }); + + it('should return 200 if requested by manager which is a member', (done) => { + models.ProjectMember.create({ + id: 3, + userId: testUtil.userIds.manager, + projectId, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }).then(() => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(200) + .end(done); + }); + }); + + it('should return 403 if requested by manager which is not a member', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(403) + .end(done); + }); + + it('should return 403 if requested by non-member copilot', (done) => { + models.ProjectMember.destroy({ + where: { userId: testUtil.userIds.copilot, projectId }, + }).then(() => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(updateBody) + .expect('Content-Type', /json/) + .expect(403) + .end(done); + }); + }); + describe('Bus api', () => { let createEventSpy; const sandbox = sinon.sandbox.create(); diff --git a/src/routes/phases/create.spec.js b/src/routes/phases/create.spec.js index fecb003f..f8c9596a 100644 --- a/src/routes/phases/create.spec.js +++ b/src/routes/phases/create.spec.js @@ -2,19 +2,28 @@ import _ from 'lodash'; import chai from 'chai'; import sinon from 'sinon'; +import config from 'config'; import request from 'supertest'; import server from '../../app'; import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; +import messageService from '../../services/messageService'; +import RabbitMQService from '../../services/rabbitmq'; +import mockRabbitMQ from '../../tests/mockRabbitMQ'; import { BUS_API_EVENT, RESOURCES, } from '../../constants'; const should = chai.should(); +const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); +const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); + const body = { name: 'test project phase', + description: 'test project phase description', + requirements: 'test project phase requirements', status: 'active', startDate: '2018-05-15T00:00:00Z', endDate: '2018-05-15T12:00:00Z', @@ -30,6 +39,8 @@ const body = { const validatePhase = (resJson, expectedPhase) => { should.exist(resJson); resJson.name.should.be.eql(expectedPhase.name); + resJson.description.should.be.eql(expectedPhase.description); + resJson.requirements.should.be.eql(expectedPhase.requirements); resJson.status.should.be.eql(expectedPhase.status); resJson.budget.should.be.eql(expectedPhase.budget); resJson.progress.should.be.eql(expectedPhase.progress); @@ -52,44 +63,43 @@ describe('Project Phases', () => { lastName: 'lName', email: 'some@abc.com', }; + const project = { + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }; let productTemplateId; - before((done) => { + beforeEach((done) => { // mocks testUtil.clearDb() - .then(() => { - models.Project.create({ - type: 'generic', - billingAccountId: 1, - name: 'test1', - description: 'test project1', - status: 'draft', - details: {}, + .then(() => models.Project.create(project).then((p) => { + projectId = p.id; + // create members + return models.ProjectMember.bulkCreate([{ + id: 1, + userId: copilotUser.userId, + projectId, + role: 'copilot', + isPrimary: false, createdBy: 1, updatedBy: 1, - lastActivityAt: 1, - lastActivityUserId: '1', - }).then((p) => { - projectId = p.id; - // create members - models.ProjectMember.bulkCreate([{ - id: 1, - userId: copilotUser.userId, - projectId, - role: 'copilot', - isPrimary: false, - createdBy: 1, - updatedBy: 1, - }, { - id: 2, - userId: memberUser.userId, - projectId, - role: 'customer', - isPrimary: true, - createdBy: 1, - updatedBy: 1, - }]); - }); - }) + }, { + id: 2, + userId: memberUser.userId, + projectId, + role: 'customer', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }]); + })) .then(() => models.ProductTemplate.create({ name: 'name 1', @@ -126,7 +136,7 @@ describe('Project Phases', () => { .then(() => done()); }); - after((done) => { + afterEach((done) => { testUtil.clearDb(done); }); @@ -345,6 +355,68 @@ describe('Project Phases', () => { }); }); + it('should return 201 if requested by admin', (done) => { + request(server) + .post(`/v5/projects/${projectId}/phases/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end(done); + }); + + it('should return 201 if requested by manager which is a member', (done) => { + models.ProjectMember.create({ + id: 3, + userId: testUtil.userIds.manager, + projectId, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }).then(() => { + request(server) + .post(`/v5/projects/${projectId}/phases/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end(done); + }); + }); + + it('should return 403 if requested by manager which is not a member', (done) => { + request(server) + .post(`/v5/projects/${projectId}/phases/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(403) + .end(done); + }); + + it('should return 403 if requested by non-member copilot', (done) => { + models.ProjectMember.destroy({ + where: { userId: testUtil.userIds.copilot, projectId }, + }).then(() => { + request(server) + .post(`/v5/projects/${projectId}/phases/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(403) + .end(done); + }); + }); + describe('Bus api', () => { let createEventSpy; const sandbox = sinon.sandbox.create(); @@ -395,5 +467,85 @@ describe('Project Phases', () => { }); }); }); + + describe('RabbitMQ Message topic', () => { + let createMessageSpy; + let publishSpy; + let sandbox; + + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + before(async () => new Promise(resolve => setTimeout(() => resolve(), 500))); + + beforeEach(async () => { + sandbox = sinon.sandbox.create(); + server.services.pubsub = new RabbitMQService(server.logger); + + // initialize RabbitMQ + server.services.pubsub.init( + config.get('rabbitmqURL'), + config.get('pubsubExchangeName'), + config.get('pubsubQueueName'), + ); + + // add project to ES index + await server.services.es.index({ + index: ES_PROJECT_INDEX, + type: ES_PROJECT_TYPE, + id: projectId, + body: { + doc: project, + }, + }); + + return new Promise(resolve => setTimeout(() => { + publishSpy = sandbox.spy(server.services.pubsub, 'publish'); + createMessageSpy = sandbox.spy(messageService, 'createTopic'); + resolve(); + }, 500)); + }); + + afterEach(() => { + sandbox.restore(); + }); + + after(() => { + mockRabbitMQ(server); + }); + + it('should send message topic when phase added', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: {}, + }), + }); + sandbox.stub(messageService, 'getClient', () => mockHttpClient); + request(server) + .post(`/v5/projects/${projectId}/phases/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(body) + .expect('Content-Type', /json/) + .expect(201) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + publishSpy.calledOnce.should.be.true; + publishSpy.calledWith('project.phase.added').should.be.true; + createMessageSpy.calledOnce.should.be.true; + createMessageSpy.calledWith(sinon.match({ reference: 'project', + referenceId: '1', + tag: 'phase#1', + title: 'test project phase', + })).should.be.true; + done(); + }); + } + }); + }); + }); }); }); diff --git a/src/routes/phases/delete.spec.js b/src/routes/phases/delete.spec.js index 2e8d04ee..99a295f9 100644 --- a/src/routes/phases/delete.spec.js +++ b/src/routes/phases/delete.spec.js @@ -3,11 +3,14 @@ import _ from 'lodash'; import request from 'supertest'; import sinon from 'sinon'; import chai from 'chai'; +import config from 'config'; import server from '../../app'; import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; - +import messageService from '../../services/messageService'; +import RabbitMQService from '../../services/rabbitmq'; +import mockRabbitMQ from '../../tests/mockRabbitMQ'; import { BUS_API_EVENT, RESOURCES, @@ -15,6 +18,9 @@ import { const should = chai.should(); // eslint-disable-line no-unused-vars +const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); +const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); + const expectAfterDelete = (projectId, id, err, next) => { if (err) throw err; setTimeout(() => @@ -66,22 +72,32 @@ describe('Project Phases', () => { lastName: 'lName', email: 'some@abc.com', }; + const project = { + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }; + const topic = { + id: 1, + title: 'test project phase', + posts: + [{ id: 1, + type: 'post', + body: 'body', + }], + }; beforeEach((done) => { // mocks testUtil.clearDb() .then(() => { - 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) => { + models.Project.create(project).then((p) => { projectId = p.id; // create members models.ProjectMember.bulkCreate([{ @@ -162,9 +178,64 @@ describe('Project Phases', () => { .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) + .expect(204) .end(err => expectAfterDelete(projectId, phaseId, err, done)); }); + it('should return 204 if requested by admin', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end(done); + }); + + it('should return 204 if requested by manager which is a member', (done) => { + models.ProjectMember.create({ + id: 3, + userId: testUtil.userIds.manager, + projectId, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }).then(() => { + request(server) + .delete(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(204) + .end(done); + }); + }); + + it('should return 403 if requested by manager which is not a member', (done) => { + request(server) + .delete(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(403) + .end(done); + }); + + it('should return 403 if requested by non-member copilot', (done) => { + models.ProjectMember.destroy({ + where: { userId: testUtil.userIds.copilot, projectId }, + }).then(() => { + request(server) + .delete(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(403) + .end(done); + }); + }); + describe('Bus api', () => { let createEventSpy; const sandbox = sinon.sandbox.create(); @@ -205,5 +276,80 @@ describe('Project Phases', () => { }); }); }); + + describe('RabbitMQ Message topic', () => { + let deleteTopicSpy; + let deletePostsSpy; + let publishSpy; + let sandbox; + + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + before(async () => new Promise(resolve => setTimeout(() => resolve(), 500))); + + beforeEach(async () => { + sandbox = sinon.sandbox.create(); + server.services.pubsub = new RabbitMQService(server.logger); + + // initialize RabbitMQ + server.services.pubsub.init( + config.get('rabbitmqURL'), + config.get('pubsubExchangeName'), + config.get('pubsubQueueName'), + ); + + // add project to ES index + await server.services.es.index({ + index: ES_PROJECT_INDEX, + type: ES_PROJECT_TYPE, + id: projectId, + body: { + doc: _.assign(project, { phases: [_.assign(body, { id: phaseId, projectId })] }), + }, + }); + + return new Promise(resolve => setTimeout(() => { + publishSpy = sandbox.spy(server.services.pubsub, 'publish'); + deleteTopicSpy = sandbox.spy(messageService, 'deleteTopic'); + deletePostsSpy = sandbox.spy(messageService, 'deletePosts'); + sandbox.stub(messageService, 'getTopicByTag', () => Promise.resolve(topic)); + resolve(); + }, 500)); + }); + + afterEach(() => { + sandbox.restore(); + }); + + after(() => { + mockRabbitMQ(server); + }); + + it('should send message topic when phase deleted', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + delete: () => Promise.resolve(true), + }); + sandbox.stub(messageService, 'getClient', () => mockHttpClient); + request(server) + .delete(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + publishSpy.calledOnce.should.be.true; + publishSpy.firstCall.calledWith('project.phase.removed').should.be.true; + deleteTopicSpy.calledOnce.should.be.true; + deleteTopicSpy.calledWith(topic.id).should.be.true; + deletePostsSpy.calledWith(topic.id).should.be.true; + done(); + }); + } + }); + }); + }); }); }); diff --git a/src/routes/phases/update.spec.js b/src/routes/phases/update.spec.js index 19ea1262..2871e57d 100644 --- a/src/routes/phases/update.spec.js +++ b/src/routes/phases/update.spec.js @@ -2,21 +2,26 @@ import _ from 'lodash'; import sinon from 'sinon'; import chai from 'chai'; +import config from 'config'; import request from 'supertest'; import server from '../../app'; import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; +import messageService from '../../services/messageService'; +import RabbitMQService from '../../services/rabbitmq'; +import mockRabbitMQ from '../../tests/mockRabbitMQ'; +import { BUS_API_EVENT } from '../../constants'; -import { - BUS_API_EVENT, - RESOURCES, -} from '../../constants'; +const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); +const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); const should = chai.should(); const body = { name: 'test project phase', + description: 'test project phase description', + requirements: 'test project phase requirements', status: 'active', startDate: '2018-05-15T00:00:00Z', endDate: '2018-05-15T12:00:00Z', @@ -31,6 +36,8 @@ const body = { const updateBody = { name: 'test project phase xxx', + description: 'test project phase description xxx', + requirements: 'test project phase requirements xxx', status: 'inactive', startDate: '2018-05-11T00:00:00Z', endDate: '2018-05-12T12:00:00Z', @@ -44,6 +51,8 @@ const updateBody = { const validatePhase = (resJson, expectedPhase) => { should.exist(resJson); resJson.name.should.be.eql(expectedPhase.name); + resJson.description.should.be.eql(expectedPhase.description); + resJson.requirements.should.be.eql(expectedPhase.requirements); resJson.status.should.be.eql(expectedPhase.status); resJson.budget.should.be.eql(expectedPhase.budget); resJson.progress.should.be.eql(expectedPhase.progress); @@ -54,6 +63,7 @@ describe('Project Phases', () => { let projectId; let phaseId; let phaseId2; + let phaseId3; const memberUser = { handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, @@ -68,22 +78,32 @@ describe('Project Phases', () => { lastName: 'lName', email: 'some@abc.com', }; - before((done) => { + const project = { + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'test project1', + status: 'draft', + details: {}, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + }; + const topic = { + id: 1, + title: 'test project phase', + posts: + [{ id: 1, + type: 'post', + body: 'body', + }], + }; + beforeEach((done) => { // mocks testUtil.clearDb() .then(() => { - 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) => { + models.Project.create(project).then((p) => { projectId = p.id; // create members models.ProjectMember.bulkCreate([{ @@ -107,11 +127,13 @@ describe('Project Phases', () => { const phases = [ body, _.assign({ order: 1 }, body), + _.assign({}, body, { status: 'draft' }), ]; models.ProjectPhase.bulkCreate(phases, { returning: true }) .then((createdPhases) => { phaseId = createdPhases[0].id; phaseId2 = createdPhases[1].id; + phaseId3 = createdPhases[2].id; done(); }); @@ -120,7 +142,7 @@ describe('Project Phases', () => { }); }); - after((done) => { + afterEach((done) => { testUtil.clearDb(done); }); @@ -267,6 +289,68 @@ describe('Project Phases', () => { }); }); + it('should return 200 if requested by admin', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(_.assign({ order: 1 }, updateBody)) + .expect('Content-Type', /json/) + .expect(200) + .end(done); + }); + + it('should return 200 if requested by manager which is a member', (done) => { + models.ProjectMember.create({ + id: 3, + userId: testUtil.userIds.manager, + projectId, + role: 'manager', + isPrimary: false, + createdBy: 1, + updatedBy: 1, + }).then(() => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(_.assign({ order: 1 }, updateBody)) + .expect('Content-Type', /json/) + .expect(200) + .end(done); + }); + }); + + it('should return 403 if requested by manager which is not a member', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .send(_.assign({ order: 1 }, updateBody)) + .expect('Content-Type', /json/) + .expect(403) + .end(done); + }); + + it('should return 403 if requested by non-member copilot', (done) => { + models.ProjectMember.destroy({ + where: { userId: testUtil.userIds.copilot, projectId }, + }).then(() => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send(_.assign({ order: 1 }, updateBody)) + .expect('Content-Type', /json/) + .expect(403) + .end(done); + }); + }); + describe('Bus api', () => { let createEventSpy; const sandbox = sinon.sandbox.create(); @@ -284,7 +368,155 @@ describe('Project Phases', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when startDate updated', (done) => { + it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when spentBudget updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + spentBudget: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_PAYMENT); + done(); + }); + } + }); + }); + + + it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when progress updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + progress: 50, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(1); + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED); + done(); + }); + } + }); + }); + + it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + details: { + text: 'something', + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_SCOPE); + done(); + }); + } + }); + }); + + it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (completed)', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + status: 'completed', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED); + done(); + }); + } + }); + }); + + it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (active)', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId3}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + status: 'active', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_TRANSITION_ACTIVE); + done(); + }); + } + }); + }); + + it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when budget updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + budget: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED); + done(); + }); + } + }); + }); + + it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when startDate updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ @@ -300,20 +532,16 @@ describe('Project Phases', () => { done(err); } else { testUtil.wait(() => { - // createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ resource: RESOURCES.PHASE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ id: phaseId })).should.be.true; + createEventSpy.calledOnce.should.be.true; createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ updatedBy: testUtil.userIds.copilot })).should.be.true; + sinon.match.has('startDate')).should.be.true; done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when duration updated', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when duration updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ @@ -337,6 +565,134 @@ describe('Project Phases', () => { } }); }); + + it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when order updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + order: 100, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED); + done(); + }); + } + }); + }); + + it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when endDate updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + endDate: new Date(), + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.calledOnce.should.be.true; + createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED); + done(); + }); + } + }); + }); + }); + + describe('RabbitMQ Message topic', () => { + let updateMessageSpy; + let publishSpy; + let sandbox; + + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + before(async () => new Promise(resolve => setTimeout(() => resolve(), 500))); + + beforeEach(async () => { + sandbox = sinon.sandbox.create(); + server.services.pubsub = new RabbitMQService(server.logger); + + // initialize RabbitMQ + server.services.pubsub.init( + config.get('rabbitmqURL'), + config.get('pubsubExchangeName'), + config.get('pubsubQueueName'), + ); + + // add project to ES index + await server.services.es.index({ + index: ES_PROJECT_INDEX, + type: ES_PROJECT_TYPE, + id: projectId, + body: { + doc: _.assign(project, { phases: [_.assign(body, { id: phaseId, projectId })] }), + }, + }); + + return new Promise(resolve => setTimeout(() => { + publishSpy = sandbox.spy(server.services.pubsub, 'publish'); + updateMessageSpy = sandbox.spy(messageService, 'updateTopic'); + sandbox.stub(messageService, 'getTopicByTag', () => Promise.resolve(topic)); + resolve(); + }, 500)); + }); + + afterEach(() => { + sandbox.restore(); + }); + + after(() => { + mockRabbitMQ(server); + }); + + it('should send message topic when phase Updated', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: {}, + }), + }); + sandbox.stub(messageService, 'getClient', () => mockHttpClient); + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(_.assign(updateBody, { budget: 123 })) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + publishSpy.calledOnce.should.be.true; + publishSpy.calledWith('project.phase.updated').should.be.true; + updateMessageSpy.calledOnce.should.be.true; + updateMessageSpy.calledWith(topic.id, sinon.match({ + title: updateBody.name, + postId: topic.posts[0].id, + content: topic.posts[0].body })).should.be.true; + done(); + }); + } + }); + }); }); }); }); diff --git a/src/routes/planConfig/version/getVersion.js b/src/routes/planConfig/version/getVersion.js index ca0179f1..7673c3cf 100644 --- a/src/routes/planConfig/version/getVersion.js +++ b/src/routes/planConfig/version/getVersion.js @@ -44,7 +44,7 @@ module.exports = [ .then((data) => { if (data.length === 0) { req.log.debug('No planConfig found in ES'); - models.PlanConfig.findOneWithLatestRevision(req.params) + return models.PlanConfig.findOneWithLatestRevision(req.params) .then((planConfig) => { // Not found if (!planConfig) { @@ -56,10 +56,10 @@ module.exports = [ return Promise.resolve(); }) .catch(next); - } else { - req.log.debug('planConfigs found in ES'); - res.json(data[0].inner_hits.planConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle } + req.log.debug('planConfigs found in ES'); + res.json(data[0].inner_hits.planConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + return Promise.resolve(); }) .catch(next), ]; diff --git a/src/routes/priceConfig/version/getVersion.js b/src/routes/priceConfig/version/getVersion.js index 2121ab59..c6b9120e 100644 --- a/src/routes/priceConfig/version/getVersion.js +++ b/src/routes/priceConfig/version/getVersion.js @@ -44,7 +44,7 @@ module.exports = [ .then((data) => { if (data.length === 0) { req.log.debug('No priceConfig found in ES'); - models.PriceConfig.findOneWithLatestRevision(req.params) + return models.PriceConfig.findOneWithLatestRevision(req.params) .then((priceConfig) => { // Not found if (!priceConfig) { @@ -56,10 +56,10 @@ module.exports = [ return Promise.resolve(); }) .catch(next); - } else { - req.log.debug('priceConfigs found in ES'); - res.json(data[0].inner_hits.priceConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle } + req.log.debug('priceConfigs found in ES'); + res.json(data[0].inner_hits.priceConfigs.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle + return Promise.resolve(); }) .catch(next); }, diff --git a/src/routes/productTemplates/list.js b/src/routes/productTemplates/list.js index 3dab4edd..f1816d86 100644 --- a/src/routes/productTemplates/list.js +++ b/src/routes/productTemplates/list.js @@ -10,22 +10,31 @@ const permissions = tcMiddleware.permissions; module.exports = [ permissions('productTemplate.view'), (req, res, next) => { - const filters = util.parseQueryFilter(req.query.filter); - if (!util.isValidFilter(filters, ['productKey'])) { - return util.handleError('Invalid filters', null, req, next); - } - const where = { deletedAt: { $eq: null }, disabled: false }; - if (filters.productKey) { - where.productKey = { $eq: filters.productKey }; - } - return models.ProductTemplate.findAll({ - where, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((productTemplates) => { - res.json(productTemplates); - }) - .catch(next); + util.fetchFromES('productTemplates') + .then((data) => { + const filters = req.query; + if (!util.isValidFilter(filters, ['productKey'])) { + util.handleError('Invalid filters', null, req, next); + } + const where = { deletedAt: { $eq: null }, disabled: false }; + if (filters.productKey) { + where.productKey = { $eq: filters.productKey }; + } + if (data.productTemplates.length === 0) { + req.log.debug('No productTemplate found in ES'); + models.ProductTemplate.findAll({ + where, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }) + .then((productTemplates) => { + res.json(productTemplates); + }) + .catch(next); + } else { + req.log.debug('productTemplates found in ES'); + res.json(data.productTemplates); + } + }); }, ]; diff --git a/src/routes/productTemplates/list.spec.js b/src/routes/productTemplates/list.spec.js index 37e21666..09af4696 100644 --- a/src/routes/productTemplates/list.spec.js +++ b/src/routes/productTemplates/list.spec.js @@ -12,7 +12,7 @@ import testUtil from '../../tests/util'; const should = chai.should(); // eslint-disable-line no-unused-vars const validateProductTemplates = (count, resJson, expectedTemplates) => { - resJson.length.should.be.eql(count); + resJson.should.have.length(count); resJson.forEach((pt, idx) => { pt.should.include.all.keys('id', 'name', 'productKey', 'category', 'subCategory', 'icon', 'brief', 'details', @@ -192,7 +192,7 @@ describe('LIST product templates', () => { .expect(200) .end((err, res) => { const resJson = res.body; - validateProductTemplates(1, [resJson[1]], [templates[1]]); + validateProductTemplates(1, resJson, [templates[1]]); done(); }); }); From 0718a234fa9c4afa2c49b08020c0f053f9b2764b Mon Sep 17 00:00:00 2001 From: gets0ul Date: Sat, 19 Oct 2019 20:55:26 +0700 Subject: [PATCH 10/88] Fixes based on feedback part 2. --- src/routes/phaseProducts/create.spec.js | 4 +- src/routes/phases/update.spec.js | 224 ++------------ src/routes/productCategories/create.js | 2 +- src/routes/productTemplates/upgrade.spec.js | 2 +- src/routes/projectMemberInvites/create.js | 48 +-- .../projectMemberInvites/create.spec.js | 64 ++-- .../projectMemberInvites/update.spec.js | 11 +- src/routes/projectTemplates/list.js | 35 ++- src/routes/projectTypes/create.js | 4 +- src/routes/projectUpgrade/create.js | 4 +- src/routes/projectUpgrade/create.spec.js | 2 +- src/routes/projects/create.js | 4 +- src/routes/projects/create.spec.js | 273 +++++++++++++++++- src/routes/timelines/create.js | 21 +- src/routes/timelines/list.spec.js | 49 +++- 15 files changed, 402 insertions(+), 345 deletions(-) diff --git a/src/routes/phaseProducts/create.spec.js b/src/routes/phaseProducts/create.spec.js index b4f104e7..d9ebba39 100644 --- a/src/routes/phaseProducts/create.spec.js +++ b/src/routes/phaseProducts/create.spec.js @@ -188,7 +188,7 @@ describe('Phase Products', () => { request(server) .post(`/v5/projects/${projectId}/phases/99999/products`) .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, }) .send(body) .expect('Content-Type', /json/) @@ -199,7 +199,7 @@ describe('Phase Products', () => { request(server) .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send(body) .expect('Content-Type', /json/) diff --git a/src/routes/phases/update.spec.js b/src/routes/phases/update.spec.js index 2871e57d..77630b8e 100644 --- a/src/routes/phases/update.spec.js +++ b/src/routes/phases/update.spec.js @@ -11,7 +11,10 @@ import busApi from '../../services/busApi'; import messageService from '../../services/messageService'; import RabbitMQService from '../../services/rabbitmq'; import mockRabbitMQ from '../../tests/mockRabbitMQ'; -import { BUS_API_EVENT } from '../../constants'; +import { + BUS_API_EVENT, + RESOURCES, +} from '../../constants'; const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); @@ -63,7 +66,6 @@ describe('Project Phases', () => { let projectId; let phaseId; let phaseId2; - let phaseId3; const memberUser = { handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, @@ -133,7 +135,6 @@ describe('Project Phases', () => { .then((createdPhases) => { phaseId = createdPhases[0].id; phaseId2 = createdPhases[1].id; - phaseId3 = createdPhases[2].id; done(); }); @@ -184,7 +185,7 @@ describe('Project Phases', () => { request(server) .patch(`/v5/projects/${projectId}/phases/999`) .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send(updateBody) .expect('Content-Type', /json/) @@ -195,7 +196,7 @@ describe('Project Phases', () => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ progress: -15, @@ -208,7 +209,7 @@ describe('Project Phases', () => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, + Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ endDate: '2018-05-13T00:00:00Z', @@ -355,168 +356,24 @@ describe('Project Phases', () => { let createEventSpy; const sandbox = sinon.sandbox.create(); + 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'); }); + afterEach(() => { sandbox.restore(); }); - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when spentBudget updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - spentBudget: 123, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_PAYMENT); - done(); - }); - } - }); - }); - - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when progress updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - progress: 50, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.callCount.should.be.eql(1); - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED); - done(); - }); - } - }); - }); - - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when details updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - details: { - text: 'something', - }, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATE_SCOPE); - done(); - }); - } - }); - }); - - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (completed)', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - status: 'completed', - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED); - done(); - }); - } - }); - }); - - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when status updated (active)', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId3}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - status: 'active', - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_TRANSITION_ACTIVE); - done(); - }); - } - }); - }); - - it('should NOT send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when budget updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - budget: 123, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED); - done(); - }); - } - }); - }); - - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when startDate updated', (done) => { + it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when startDate updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ @@ -532,16 +389,21 @@ describe('Project Phases', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; + // createEventSpy.calledOnce.should.be.true; createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match.has('startDate')).should.be.true; + sinon.match({ resource: RESOURCES.PHASE })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, + sinon.match({ id: phaseId })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, + sinon.match({ updatedBy: testUtil.userIds.copilot })).should.be.true; done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when duration updated', (done) => { + + it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when duration updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ @@ -565,54 +427,6 @@ describe('Project Phases', () => { } }); }); - - it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when order updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - order: 100, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED); - done(); - }); - } - }); - }); - - it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when endDate updated', (done) => { - request(server) - .patch(`/v5/projects/${projectId}/phases/${phaseId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .send({ - endDate: new Date(), - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED); - done(); - }); - } - }); - }); }); describe('RabbitMQ Message topic', () => { diff --git a/src/routes/productCategories/create.js b/src/routes/productCategories/create.js index 65c9413e..92b612c4 100644 --- a/src/routes/productCategories/create.js +++ b/src/routes/productCategories/create.js @@ -40,7 +40,7 @@ module.exports = [ }); // Check if duplicated key - return models.ProductCategory.findByPk(req.body.key) + return models.ProductCategory.findByPk(req.body.key, { paranoid: false }) .then((existing) => { if (existing) { const apiErr = new Error(`Product category already exists for key ${req.params.key}`); diff --git a/src/routes/productTemplates/upgrade.spec.js b/src/routes/productTemplates/upgrade.spec.js index d3678541..9d39c696 100644 --- a/src/routes/productTemplates/upgrade.spec.js +++ b/src/routes/productTemplates/upgrade.spec.js @@ -86,7 +86,7 @@ describe('UPGRADE product template', () => { ])) .then(() => { const config = { - questions: [{ + sections: [{ id: 'appDefinition', title: 'Sample Project', required: true, diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 8f1a9827..9d844ffa 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -7,9 +7,8 @@ import config from 'config'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, MANAGER_ROLES, INVITE_STATUS, - EVENT, RESOURCES, BUS_API_EVENT, USER_ROLE, MAX_PARALLEL_REQUEST_QTY } from '../../constants'; -import { createEvent } from '../../services/busApi'; +import { PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, + MANAGER_ROLES, INVITE_STATUS, EVENT, RESOURCES, USER_ROLE, MAX_PARALLEL_REQUEST_QTY } from '../../constants'; /** * API to create member invite to project. @@ -140,7 +139,6 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed, members) invitePromises.push(models.ProjectMemberInvite.create(dataNew)); }); - // remove invites for users that are invited already _.remove(nonExistentUserEmails, email => _.some(invites, (i) => { @@ -173,9 +171,8 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed, members) return invitePromises; }; -const sendInviteEmail = (req, projectId, invite) => { +const sendInviteEmail = (req, projectId) => { req.log.debug(req.authUser); - const emailEventType = BUS_API_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; const promises = [ models.Project.findOne({ where: { id: projectId }, @@ -185,40 +182,6 @@ const sendInviteEmail = (req, projectId, invite) => { ]; return Promise.all(promises).then((responses) => { req.log.debug(responses); - const project = responses[0]; - const initiator = responses[1] && responses[1].length ? responses[1][0] : { - userId: req.authUser.userId, - firstName: 'Connect', - lastName: 'User', - }; - createEvent(emailEventType, { - data: { - connectURL: config.get('connectUrl'), - accountsAppURL: config.get('accountsAppUrl'), - subject: config.get('inviteEmailSubject'), - projects: [{ - name: project.name, - projectId, - sections: [ - { - EMAIL_INVITES: true, - title: config.get('inviteEmailSectionTitle'), - projectName: project.name, - projectId, - initiator, - isSSO: util.isSSO(project), - }, - ], - }], - }, - recipients: [invite.email], - version: 'v3', - from: { - name: config.get('EMAIL_INVITE_FROM_NAME'), - email: config.get('EMAIL_INVITE_FROM_EMAIL'), - }, - categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], - }, req.log); }).catch((error) => { req.log.error(error); }); @@ -316,8 +279,7 @@ module.exports = [ }; req.log.debug('Creating invites'); - const invitePromises = buildCreateInvitePromises(req, invite, invites, data, failed, members); - return models.Sequelize.Promise.all(invitePromises) + return models.Sequelize.Promise.all(buildCreateInvitePromises(req, invite, invites, data, failed, members)) .then((values) => { values.forEach((v) => { // emit the event @@ -334,7 +296,7 @@ module.exports = [ ); // send email invite (async) if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) { - sendInviteEmail(req, projectId, v); + sendInviteEmail(req, projectId); } }); return values; diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index fa2ad92a..ea9ceaf0 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -456,17 +456,9 @@ describe('Project Member Invite create', () => { get: () => Promise.resolve({ status: 200, data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: { - success: [{ - roleName: USER_ROLE.COPILOT, - }], - }, - }, + success: [{ + roleName: USER_ROLE.COPILOT, + }], }, }), }); @@ -502,17 +494,9 @@ describe('Project Member Invite create', () => { get: () => Promise.resolve({ status: 200, data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: { - success: [{ - roleName: USER_ROLE.COPILOT, - }], - }, - }, + success: [{ + roleName: USER_ROLE.COPILOT, + }], }, }), }); @@ -789,6 +773,8 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); + resJson[0].email.should.equal('duplicate_lowercase@test.com'); + resJson[0].message.should.equal('User with such email is already invited to this project.'); resJson.length.should.equal(1); done(); } @@ -813,6 +799,8 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); + 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(); } @@ -838,6 +826,7 @@ describe('Project Member Invite create', () => { } else { const resJson = res.body.failed; should.exist(resJson); + resJson[0].email.should.equal('WITHdot@gmail.com'); resJson.length.should.equal(1); done(); } @@ -864,6 +853,7 @@ describe('Project Member Invite create', () => { const resJson = res.body.failed; should.exist(resJson); resJson.length.should.equal(1); + resJson[0].email.should.equal('WITHOUT.dot@gmail.com'); done(); } }); @@ -885,17 +875,9 @@ describe('Project Member Invite create', () => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: [{ - roleName: USER_ROLE.MANAGER, - }], - }, - }, + data: [{ + roleName: USER_ROLE.MANAGER, + }], }), }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); @@ -934,17 +916,9 @@ describe('Project Member Invite create', () => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: [{ - roleName: USER_ROLE.MANAGER, - }], - }, - }, + data: [{ + roleName: USER_ROLE.MANAGER, + }], }), }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); @@ -963,7 +937,7 @@ describe('Project Member Invite create', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledTwice.should.be.true; + createEventSpy.calledOnce.should.be.true; createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED).should.be.true; createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE })).should.be.true; diff --git a/src/routes/projectMemberInvites/update.spec.js b/src/routes/projectMemberInvites/update.spec.js index 4080ab45..81de61f6 100644 --- a/src/routes/projectMemberInvites/update.spec.js +++ b/src/routes/projectMemberInvites/update.spec.js @@ -306,16 +306,7 @@ describe('Project member invite update', () => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, - data: { - id: 'requesterId', - version: 'v3', - result: { - success: true, - status: 200, - content: [{ - }], - }, - }, + data: {}, }), }); sandbox.stub(util, 'getHttpClient', () => mockHttpClient); diff --git a/src/routes/projectTemplates/list.js b/src/routes/projectTemplates/list.js index b89ff43a..45be8e17 100644 --- a/src/routes/projectTemplates/list.js +++ b/src/routes/projectTemplates/list.js @@ -3,21 +3,32 @@ */ import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; +import util from '../../util'; const permissions = tcMiddleware.permissions; module.exports = [ permissions('projectTemplate.view'), - (req, res, next) => models.ProjectTemplate.findAll({ - where: { - deletedAt: { $eq: null }, - disabled: false, - }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }) - .then((projectTemplates) => { - res.json(projectTemplates); - }) - .catch(next), + (req, res, next) => { + util.fetchFromES('projectTemplates') + .then((data) => { + if (data.projectTemplates.length === 0) { + req.log.debug('No projectTemplate found in ES'); + models.ProjectTemplate.findAll({ + where: { + deletedAt: { $eq: null }, + disabled: false, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }).then((projectTemplates) => { + res.json(projectTemplates); + }) + .catch(next); + } else { + req.log.debug('projectTemplates found in ES'); + res.json(data.projectTemplates); + } + }); + }, ]; diff --git a/src/routes/projectTypes/create.js b/src/routes/projectTypes/create.js index 5f2f6fde..88051a51 100644 --- a/src/routes/projectTypes/create.js +++ b/src/routes/projectTypes/create.js @@ -41,10 +41,10 @@ module.exports = [ }); // Check if duplicated key - return models.ProjectType.findByPk(req.body.key) + return models.ProjectType.findByPk(req.body.key, { paranoid: false }) .then((existing) => { if (existing) { - const apiErr = new Error(`Project type already exists for key ${req.params.key}`); + const apiErr = new Error(`Project type already exists (may be deleted) for key ${req.body.key}`); apiErr.status = 400; return Promise.reject(apiErr); } diff --git a/src/routes/projectUpgrade/create.js b/src/routes/projectUpgrade/create.js index a1fe2fc2..edc12152 100644 --- a/src/routes/projectUpgrade/create.js +++ b/src/routes/projectUpgrade/create.js @@ -45,9 +45,9 @@ async function findCompletedProjectEndDate(projectId, transaction) { */ function applyTemplate(template, source, destination) { if (!template || typeof template !== 'object') { return; } - if (!template.questions || !template.questions.length) { return; } + if (!template.sections || !template.sections.length) { return; } // questions field is actually array of sections - const templateQuestions = template.questions; + const templateQuestions = template.sections; // loop through for every section templateQuestions.forEach((section) => { // find subsections diff --git a/src/routes/projectUpgrade/create.spec.js b/src/routes/projectUpgrade/create.spec.js index 9ce56413..c0a1b680 100644 --- a/src/routes/projectUpgrade/create.spec.js +++ b/src/routes/projectUpgrade/create.spec.js @@ -107,7 +107,7 @@ describe('Project upgrade', () => { alias2: [1, 2, 3], }, template: { - questions: [ + sections: [ { subSections: [ { fieldName: 'details.name' }, diff --git a/src/routes/projects/create.js b/src/routes/projects/create.js index 4827122c..9e5214da 100644 --- a/src/routes/projects/create.js +++ b/src/routes/projects/create.js @@ -24,7 +24,7 @@ const traverse = require('traverse'); */ const permissions = require('tc-core-library-js').middleware.permissions; -const createProjectValdiations = { +const createProjectValidations = { body: Joi.object().keys({ name: Joi.string().required(), description: Joi.string().allow(null).allow('').optional(), @@ -375,7 +375,7 @@ function validateAndFetchTemplates(templateId) { module.exports = [ // handles request validations - validate(createProjectValdiations), + validate(createProjectValidations), permissions('project.create'), fieldLookupValidation(models.ProjectType, 'key', 'body.type', 'Project type'), /** diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index 67bd0169..02497846 100644 --- a/src/routes/projects/create.spec.js +++ b/src/routes/projects/create.spec.js @@ -157,6 +157,96 @@ describe('Project create', () => { createdBy: 1, updatedBy: 2, }, + { + id: 4, + name: 'template with workstreams', + key: 'key 3', + category: 'category 3', + icon: 'http://example.com/icon3.ico', + question: 'question 3', + info: 'info 3', + aliases: [], + scope: {}, + phases: { + workstreamsConfig: { + projectFieldName: 'details.appDefinition.deliverables', + workstreamTypesToProjectValues: { + development: [ + 'dev-qa', + ], + design: [ + 'design', + ], + deployment: [ + 'deployment', + ], + qa: [ + 'dev-qa', + ], + }, + workstreams: [ + { + name: 'Design Workstream', + type: 'design', + }, + { + name: 'Development Workstream', + type: 'development', + }, + { + name: 'QA Workstream', + type: 'qa', + }, + { + name: 'Deployment Workstream', + typ: 'deployment', + }, + ], + }, + }, + createdBy: 1, + updatedBy: 2, + }, + ])) + .then(() => models.BuildingBlock.bulkCreate([ + { + id: 1, + key: 'BLOCK_KEY', + config: {}, + privateConfig: { + priceItems: { + community: 3456, + topcoder_service: '19%', + fee: 1234, + }, + }, + createdBy: 1, + updatedBy: 2, + }, + { + id: 2, + key: 'BLOCK_KEY2', + config: {}, + privateConfig: { + message: 'invalid config', + }, + createdBy: 1, + updatedBy: 2, + }, + { + id: 3, + key: 'BLOCK_KEY3', + config: {}, + privateConfig: { + priceItems: { + community: '34%', + topcoder_service: 6789, + fee: '56%', + }, + }, + createdBy: 1, + updatedBy: 2, + }, ])) .then(() => done()); }); @@ -453,6 +543,80 @@ describe('Project create', () => { }); }); + it('should create project with workstreams if template has them defined', (done) => { + 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(_.merge({ + templateId: 4, + details: { + appDefinition: { + deliverables: ['dev-qa', 'design'], + }, + }, + }, body)) + .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('draft'); + resJson.type.should.be.eql(body.type); + 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.bookmarks.should.have.lengthOf(1); + resJson.bookmarks[0].title.should.be.eql('title1'); + resJson.bookmarks[0].address.should.be.eql('http://www.address.com'); + resJson.phases.should.have.lengthOf(0); + server.services.pubsub.publish.calledWith('project.draft-created').should.be.true; + + // verify that project has been marked to use workstreams + resJson.details.settings.workstreams.should.be.true; + + // Check Workstreams records are created correctly + models.WorkStream.findAll({ + where: { + projectId: resJson.id, + }, + raw: true, + }).then((workStreams) => { + workStreams.length.should.be.eql(3); + _.filter(workStreams, { type: 'development', name: 'Development Workstream' }).length.should.be.eql(1); + _.filter(workStreams, { type: 'design', name: 'Design Workstream' }).length.should.be.eql(1); + _.filter(workStreams, { type: 'qa', name: 'QA Workstream' }).length.should.be.eql(1); + done(); + }).catch(done); + } + }); + }); + it('should return 201 if valid user and data (with estimation)', (done) => { const validBody = _.cloneDeep(body); validBody.estimation = [ @@ -584,7 +748,7 @@ describe('Project create', () => { projectEstimations[0].metadata.deliverable.should.be.eql('design'); projectEstimations[0].buildingBlockKey.should.be.eql('ZEPLIN_APP_ADDON_CA'); done(); - }); + }).catch(done); } }); }); @@ -672,5 +836,112 @@ describe('Project create', () => { } }); }); + + it('should create correct estimation items with estimation', (done) => { + const validBody = _.cloneDeep(body); + validBody.estimation = [ + { + conditions: '( HAS_DEV_DELIVERABLE && (ONLY_ONE_OS_MOBILE) )', + price: 1000, + minTime: 2, + maxTime: 2, + metadata: {}, + buildingBlockKey: 'BLOCK_KEY', + }, + { + conditions: '( HAS_DEV_DELIVERABLE && (ONLY_ONE_OS_MOBILE) )', + price: 1000, + minTime: 2, + maxTime: 2, + metadata: {}, + buildingBlockKey: 'BLOCK_KEY2', + }, + { + conditions: '( HAS_DEV_DELIVERABLE && (ONLY_ONE_OS_MOBILE) )', + price: 1000, + minTime: 2, + maxTime: 2, + metadata: {}, + buildingBlockKey: 'BLOCK_KEY3', + }, + ]; + validBody.templateId = 3; + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: { + 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.name); + should.exist(resJson.estimations); + resJson.estimations.length.should.be.eql(3); + + const totalPromises = []; + // check estimation items one by one + _.forEach(resJson.estimations, estimation => models.ProjectEstimationItem.findAll({ + where: { + projectEstimationId: estimation.id, + }, + raw: true, + }).then((items) => { + totalPromises.concat(_.map(items, (item) => { + should.exist(item.type); + should.exist(item.price); + should.exist(item.markupUsedReference); + should.exist(item.markupUsedReferenceId); + + item.markupUsedReference.should.be.eql('buildingBlock'); + if (estimation.buildingBlockKey === 'BLOCK_KEY') { + if (item.type === 'community') { + item.price.should.be.eql(3456); + } else if (item.type === 'topcoder_service') { + item.price.should.be.eql(190); + } else if (item.type === 'fee') { + item.price.should.be.eql(1234); + } else { + return Promise.reject('estimation item type is not correct'); + } + } else if (estimation.buildingBlockKey === 'BLOCK_KEY2') { + return Promise.reject('should not create estimation item for invalid building block'); + } else if (estimation.buildingBlockKey === 'BLOCK_KEY3') { + if (item.type === 'community') { + item.price.should.be.eql(340); + } else if (item.type === 'topcoder_service') { + item.price.should.be.eql(6789); + } else if (item.type === 'fee') { + item.price.should.be.eql(560); + } else { + return Promise.reject('estimation item type is not correct'); + } + } else { + return Promise.reject('estimation building block key is not correct'); + } + return Promise.resolve(); + })); + })); + + Promise.all(totalPromises).then(() => { + done(); + }).catch(e => done(e)); + } + }); + }); }); }); diff --git a/src/routes/timelines/create.js b/src/routes/timelines/create.js index fbc361c2..cb2e09d0 100644 --- a/src/routes/timelines/create.js +++ b/src/routes/timelines/create.js @@ -6,9 +6,10 @@ import _ from 'lodash'; import Joi from 'joi'; import moment from 'moment'; import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; import validateTimeline from '../../middlewares/validateTimeline'; import models from '../../models'; -import { EVENT, TIMELINE_REFERENCES, MILESTONE_STATUS, MILESTONE_TEMPLATE_REFERENCES } +import { EVENT, RESOURCES, TIMELINE_REFERENCES, MILESTONE_STATUS, MILESTONE_TEMPLATE_REFERENCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -48,7 +49,7 @@ module.exports = [ let result; // Save to DB - return models.sequelize.transaction(() => { + models.sequelize.transaction(() => { req.log.debug('Started transaction'); return models.Timeline.create(entity) .then((createdEntity) => { @@ -106,7 +107,8 @@ module.exports = [ }); } return Promise.resolve(); - }); + }) + .catch(next); }) .then(() => { // Send event to bus @@ -116,6 +118,19 @@ module.exports = [ { correlationId: req.id }, ); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.TIMELINE_ADDED, + RESOURCES.TIMELINE, + result); + + // emit the event for milestones + _.map(result.milestones, milestone => util.sendResourceToKafkaBus(req, + EVENT.ROUTING_KEY.MILESTONE_ADDED, + RESOURCES.MILESTONE, + milestone)); + // Write to the response res.status(201).json(result); return Promise.resolve(); diff --git a/src/routes/timelines/list.spec.js b/src/routes/timelines/list.spec.js index 7df8e58b..3a3e9996 100644 --- a/src/routes/timelines/list.spec.js +++ b/src/routes/timelines/list.spec.js @@ -182,22 +182,31 @@ describe('LIST timelines', () => { ])) .then(() => // Create timelines - models.Timeline.bulkCreate(timelines, { returning: true })) - .then(createdTimelines => + models.Timeline.bulkCreate(timelines, { returning: true }) + .then(createdTimelines => ( + // create milestones after timelines + models.Milestone.bulkCreate(milestones)) + .then(createdMilestones => [createdTimelines, createdMilestones]), + ), + ).then(([createdTimelines, createdMilestones]) => // Index to ES - Promise.all(_.map(createdTimelines, (createdTimeline) => { - const timelineJson = _.omit(createdTimeline.toJSON(), 'deletedAt', 'deletedBy'); - timelineJson.projectId = createdTimeline.id !== 3 ? 1 : 2; - if (timelineJson.id === 1) { - timelineJson.milestones = milestones; - } - return server.services.es.index({ - index: ES_TIMELINE_INDEX, - type: ES_TIMELINE_TYPE, - id: timelineJson.id, - body: timelineJson, - }); - })) + Promise.all(_.map(createdTimelines, (createdTimeline) => { + const timelineJson = _.omit(createdTimeline.toJSON(), 'deletedAt', 'deletedBy'); + timelineJson.projectId = createdTimeline.id !== 3 ? 1 : 2; + if (timelineJson.id === 1) { + timelineJson.milestones = _.map( + createdMilestones, + cm => _.omit(cm.toJSON(), 'deletedAt', 'deletedBy'), + ); + } + + return server.services.es.index({ + index: ES_TIMELINE_INDEX, + type: ES_TIMELINE_TYPE, + id: timelineJson.id, + body: timelineJson, + }); + })) .then(() => { // sleep for some time, let elasticsearch indices be settled // sleep.sleep(5); @@ -278,6 +287,16 @@ describe('LIST timelines', () => { // Milestones resJson[0].milestones.should.have.length(2); + resJson[0].milestones.forEach((milestone) => { + // validate statusHistory + should.exist(milestone.statusHistory); + milestone.statusHistory.should.be.an('array'); + milestone.statusHistory.length.should.be.eql(1); + milestone.statusHistory.forEach((statusHistory) => { + statusHistory.reference.should.be.eql('milestone'); + statusHistory.referenceId.should.be.eql(milestone.id); + }); + }); done(); }); From 8fb8b2f9b7c715c4e079b20db3a784c308efbd7d Mon Sep 17 00:00:00 2001 From: gets0ul Date: Mon, 21 Oct 2019 13:08:35 +0700 Subject: [PATCH 11/88] Fix remaining mismatches (productCategories, projectTemplates, projectSettings, projectReports) --- src/routes/productCategories/create.js | 2 +- src/routes/projectReports/getReport.js | 2 +- src/routes/projectSettings/create.js | 29 +++--- src/routes/projectSettings/create.spec.js | 84 +++++++++-------- src/routes/projectSettings/list.js | 3 +- src/routes/projectSettings/list.spec.js | 8 +- src/routes/projectSettings/update.js | 22 +++-- src/routes/projectSettings/update.spec.js | 104 ++++++++++------------ src/routes/projectTemplates/list.js | 12 ++- 9 files changed, 129 insertions(+), 137 deletions(-) diff --git a/src/routes/productCategories/create.js b/src/routes/productCategories/create.js index 92b612c4..4abce161 100644 --- a/src/routes/productCategories/create.js +++ b/src/routes/productCategories/create.js @@ -43,7 +43,7 @@ module.exports = [ return models.ProductCategory.findByPk(req.body.key, { paranoid: false }) .then((existing) => { if (existing) { - const apiErr = new Error(`Product category already exists for key ${req.params.key}`); + const apiErr = new Error(`Product category already exists (may be deleted) for key ${req.body.key}`); apiErr.status = 400; return Promise.reject(apiErr); } diff --git a/src/routes/projectReports/getReport.js b/src/routes/projectReports/getReport.js index b2179c23..aa4253c3 100644 --- a/src/routes/projectReports/getReport.js +++ b/src/routes/projectReports/getReport.js @@ -47,7 +47,7 @@ module.exports = [ } req.log.debug(result); - return res.status(200).json(util.wrapResponse(req.id, result)); + return res.status(200).json(result); } catch (err) { req.log.error(err); return res.status(500).send(err.toString()); diff --git a/src/routes/projectSettings/create.js b/src/routes/projectSettings/create.js index d2940f00..6623a723 100644 --- a/src/routes/projectSettings/create.js +++ b/src/routes/projectSettings/create.js @@ -15,17 +15,15 @@ const schema = { params: { projectId: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - key: Joi.string().max(255).required(), - value: Joi.string().max(255).required(), - valueType: Joi.string().valid(_.values(VALUE_TYPE)).required(), - projectId: Joi.any().strip(), - metadata: Joi.object().optional(), - readPermission: Joi.object().required(), - writePermission: Joi.object().required(), - }).required(), - }, + body: Joi.object().keys({ + key: Joi.string().max(255).required(), + value: Joi.string().max(255).required(), + valueType: Joi.string().valid(_.values(VALUE_TYPE)).required(), + projectId: Joi.any().strip(), + metadata: Joi.object().optional(), + readPermission: Joi.object().required(), + writePermission: Joi.object().required(), + }).required(), }; module.exports = [ @@ -34,7 +32,7 @@ module.exports = [ (req, res, next) => { let setting = null; const projectId = req.params.projectId; - const entity = _.assign(req.body.param, { + const entity = _.assign(req.body, { createdBy: req.authUser.userId, updatedBy: req.authUser.userId, projectId, @@ -55,7 +53,7 @@ module.exports = [ includeAllProjectSettingsForInternalUsage: true, where: { projectId, - key: req.body.param.key, + key: req.body.key, }, paranoid: false, }); @@ -63,7 +61,7 @@ module.exports = [ .then((projectSetting) => { if (projectSetting) { const apiErr = new Error(`Project Setting already exists for project id ${projectId} ` + - `and key ${req.body.param.key}`); + `and key ${req.body.key}`); apiErr.status = 400; return Promise.reject(apiErr); } @@ -86,8 +84,7 @@ module.exports = [ req.log.debug('new project setting created (id# %d, key: %s)', setting.id, setting.key); // Omit deletedAt, deletedBy - res.status(201).json(util.wrapResponse( - req.id, _.omit(setting.toJSON(), 'deletedAt', 'deletedBy'), 1, 201)); + res.status(201).json(_.omit(setting.toJSON(), 'deletedAt', 'deletedBy')); }) .catch(next); }, diff --git a/src/routes/projectSettings/create.spec.js b/src/routes/projectSettings/create.spec.js index c1a14533..152ee7fa 100644 --- a/src/routes/projectSettings/create.spec.js +++ b/src/routes/projectSettings/create.spec.js @@ -74,18 +74,16 @@ describe('CREATE Project Setting', () => { let estimationId; const body = { - param: { - key: 'markup_topcoder_service', - value: '3500', - valueType: 'double', - readPermission: { - projectRoles: ['customer'], - topcoderRoles: ['administrator'], - }, - writePermission: { - allowRule: { topcoderRoles: ['administrator'] }, - denyRule: { projectRoles: ['copilot'] }, - }, + key: 'markup_topcoder_service', + value: '3500', + valueType: 'double', + readPermission: { + projectRoles: ['customer'], + topcoderRoles: ['administrator'], + }, + writePermission: { + allowRule: { topcoderRoles: ['administrator'] }, + denyRule: { projectRoles: ['copilot'] }, }, }; @@ -175,7 +173,7 @@ describe('CREATE Project Setting', () => { it('should return 400 for missing key', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.key; + delete invalidBody.key; request(server) .post(`/v5/projects/${projectId}/settings`) @@ -189,7 +187,7 @@ describe('CREATE Project Setting', () => { it('should return 400 for missing value', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.value; + delete invalidBody.value; request(server) .post(`/v5/projects/${projectId}/settings`) @@ -203,7 +201,7 @@ describe('CREATE Project Setting', () => { it('should return 400 for missing valueType', (done) => { const invalidBody = _.cloneDeep(body); - delete invalidBody.param.valueType; + delete invalidBody.valueType; request(server) .post(`/v5/projects/${projectId}/settings`) @@ -217,8 +215,8 @@ describe('CREATE Project Setting', () => { xit('should return 400 for negative value when valueType = percentage', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.value = '-10'; - invalidBody.param.valueType = VALUE_TYPE.PERCENTAGE; + invalidBody.value = '-10'; + invalidBody.valueType = VALUE_TYPE.PERCENTAGE; request(server) .post(`/v5/projects/${projectId}/settings`) @@ -232,8 +230,8 @@ describe('CREATE Project Setting', () => { xit('should return 400 for value greater than 100 when valueType = percentage', (done) => { const invalidBody = _.cloneDeep(body); - invalidBody.param.value = '150'; - invalidBody.param.valueType = VALUE_TYPE.PERCENTAGE; + invalidBody.value = '150'; + invalidBody.valueType = VALUE_TYPE.PERCENTAGE; request(server) .post(`/v5/projects/${projectId}/settings`) @@ -247,11 +245,11 @@ describe('CREATE Project Setting', () => { it('should return 400, for admin, when create key with existing key', (done) => { const existing = _.cloneDeep(body); - existing.param.projectId = projectId; - existing.param.createdBy = 1; - existing.param.updatedBy = 1; + existing.projectId = projectId; + existing.createdBy = 1; + existing.updatedBy = 1; - models.ProjectSetting.create(existing.param).then(() => { + models.ProjectSetting.create(existing).then(() => { request(server) .post(`/v5/projects/${projectId}/settings`) .set({ @@ -265,7 +263,7 @@ describe('CREATE Project Setting', () => { it('should return 201 for manager with non-estimation type, not calculating project estimation items', (done) => { const createBody = _.cloneDeep(body); - createBody.param.key = 'markup_no_estimation'; + createBody.key = 'markup_no_estimation'; request(server) .post(`/v5/projects/${projectId}/settings`) @@ -278,10 +276,10 @@ describe('CREATE Project Setting', () => { .end((err, res) => { if (err) done(err); - const resJson = res.body.result.content; - resJson.key.should.be.eql(createBody.param.key); - resJson.value.should.be.eql(createBody.param.value); - resJson.valueType.should.be.eql(createBody.param.valueType); + const resJson = res.body; + resJson.key.should.be.eql(createBody.key); + resJson.value.should.be.eql(createBody.value); + resJson.valueType.should.be.eql(createBody.valueType); resJson.projectId.should.be.eql(projectId); resJson.createdBy.should.be.eql(40051334); should.exist(resJson.createdAt); @@ -305,10 +303,10 @@ describe('CREATE Project Setting', () => { .end((err, res) => { if (err) done(err); - const resJson = res.body.result.content; - resJson.key.should.be.eql(body.param.key); - resJson.value.should.be.eql(body.param.value); - resJson.valueType.should.be.eql(body.param.valueType); + const resJson = res.body; + resJson.key.should.be.eql(body.key); + resJson.value.should.be.eql(body.value); + resJson.valueType.should.be.eql(body.valueType); resJson.projectId.should.be.eql(projectId); resJson.createdBy.should.be.eql(40051334); should.exist(resJson.createdAt); @@ -318,9 +316,9 @@ describe('CREATE Project Setting', () => { should.not.exist(resJson.deletedAt); expectAfterCreate(resJson.id, projectId, _.assign(estimation, { id: estimationId, - value: body.param.value, - valueType: body.param.valueType, - key: body.param.key, + value: body.value, + valueType: body.valueType, + key: body.key, }), 1, 0, err, done); }); }); @@ -337,10 +335,10 @@ describe('CREATE Project Setting', () => { .end((err, res) => { if (err) done(err); - const resJson = res.body.result.content; - resJson.key.should.be.eql(body.param.key); - resJson.value.should.be.eql(body.param.value); - resJson.valueType.should.be.eql(body.param.valueType); + const resJson = res.body; + resJson.key.should.be.eql(body.key); + resJson.value.should.be.eql(body.value); + resJson.valueType.should.be.eql(body.valueType); resJson.projectId.should.be.eql(projectId); resJson.createdBy.should.be.eql(40051333); should.exist(resJson.createdAt); @@ -364,10 +362,10 @@ describe('CREATE Project Setting', () => { .end((err, res) => { if (err) done(err); - const resJson = res.body.result.content; - resJson.key.should.be.eql(body.param.key); - resJson.value.should.be.eql(body.param.value); - resJson.valueType.should.be.eql(body.param.valueType); + const resJson = res.body; + resJson.key.should.be.eql(body.key); + resJson.value.should.be.eql(body.value); + resJson.valueType.should.be.eql(body.valueType); resJson.projectId.should.be.eql(projectId); resJson.createdBy.should.be.eql(40051336); resJson.updatedBy.should.be.eql(40051336); diff --git a/src/routes/projectSettings/list.js b/src/routes/projectSettings/list.js index 1668178f..480cd26c 100644 --- a/src/routes/projectSettings/list.js +++ b/src/routes/projectSettings/list.js @@ -6,7 +6,6 @@ import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; -import util from '../../util'; const permissions = tcMiddleware.permissions; @@ -44,7 +43,7 @@ module.exports = [ return models.ProjectSetting.findAll(options); }) .then((result) => { - res.json(util.wrapResponse(req.id, _.filter(result, r => r))); + res.json(_.filter(result, r => r)); }) .catch(next); }, diff --git a/src/routes/projectSettings/list.spec.js b/src/routes/projectSettings/list.spec.js index 08535645..f5e0b597 100644 --- a/src/routes/projectSettings/list.spec.js +++ b/src/routes/projectSettings/list.spec.js @@ -159,7 +159,7 @@ describe('LIST Project Settings', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(0); done(); @@ -179,7 +179,7 @@ describe('LIST Project Settings', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(0); done(); @@ -198,7 +198,7 @@ describe('LIST Project Settings', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(1); const setting = settings[0]; @@ -225,7 +225,7 @@ describe('LIST Project Settings', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.should.have.lengthOf(2); const setting = settings[0]; diff --git a/src/routes/projectSettings/update.js b/src/routes/projectSettings/update.js index df9504c4..9f22b1d9 100644 --- a/src/routes/projectSettings/update.js +++ b/src/routes/projectSettings/update.js @@ -16,16 +16,14 @@ const schema = { projectId: Joi.number().integer().positive().required(), id: Joi.number().integer().positive().required(), }, - body: { - param: Joi.object().keys({ - value: Joi.string().max(255), - valueType: Joi.string().valid(_.values(VALUE_TYPE)), - projectId: Joi.any().strip(), - metadata: Joi.object(), - readPermission: Joi.object(), - writePermission: Joi.object(), - }).required(), - }, + body: Joi.object().keys({ + value: Joi.string().max(255), + valueType: Joi.string().valid(_.values(VALUE_TYPE)), + projectId: Joi.any().strip(), + metadata: Joi.object(), + readPermission: Joi.object(), + writePermission: Joi.object(), + }).required(), }; module.exports = [ @@ -36,7 +34,7 @@ module.exports = [ let updatedSetting = null; const projectId = req.params.projectId; const id = req.params.id; - const entityToUpdate = _.assign(req.body.param, { + const entityToUpdate = _.assign(req.body, { updatedBy: req.authUser.userId, }); @@ -69,7 +67,7 @@ module.exports = [ }), ) // transaction end .then(() => { - res.json(util.wrapResponse(req.id, updatedSetting)); + res.json(updatedSetting); }) .catch(next); }, diff --git a/src/routes/projectSettings/update.spec.js b/src/routes/projectSettings/update.spec.js index fb3c2856..916757f2 100644 --- a/src/routes/projectSettings/update.spec.js +++ b/src/routes/projectSettings/update.spec.js @@ -90,29 +90,27 @@ describe('UPDATE Project Setting', () => { }; const body = { - param: { - value: '5599.96', - valueType: 'double', - readPermission: { - projectRoles: ['customer'], - topcoderRoles: ['administrator'], + value: '5599.96', + valueType: 'double', + readPermission: { + projectRoles: ['customer'], + topcoderRoles: ['administrator'], + }, + writePermission: { + allowRule: { + projectRoles: ['customer', 'copilot'], + topcoderRoles: ['administrator', 'Connect Admin'], }, - writePermission: { - allowRule: { - projectRoles: ['customer', 'copilot'], - topcoderRoles: ['administrator', 'Connect Admin'], - }, - denyRule: { - projectRoles: ['copilot'], - topcoderRoles: ['Connect Admin'], - }, + denyRule: { + projectRoles: ['copilot'], + topcoderRoles: ['Connect Admin'], }, }, }; // we don't include these params into the body, we cannot update them // but we use them for creating model directly and for checking returned values - const bodyParamNonMutable = { + const bodyNonMutable = { key: 'markup_topcoder_service', createdBy: 1, updatedBy: 1, @@ -169,7 +167,7 @@ describe('UPDATE Project Setting', () => { updatedBy: 1, }]) .then(() => { - models.ProjectSetting.create(_.assign({}, body.param, bodyParamNonMutable, { + models.ProjectSetting.create(_.assign({}, body, bodyNonMutable, { projectId, })) .then((s) => { @@ -268,9 +266,7 @@ describe('UPDATE Project Setting', () => { Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - key: 'updated_key', - }, + key: 'updated_key', }) .expect(400, done); }); @@ -278,7 +274,7 @@ describe('UPDATE Project Setting', () => { it('should return 200, for member with permission (team member), value updated but no project estimation present', (done) => { const notPresent = _.cloneDeep(body); - notPresent.param.value = '4500'; + notPresent.value = '4500'; models.ProjectEstimation.destroy({ where: { @@ -297,31 +293,29 @@ describe('UPDATE Project Setting', () => { Authorization: `Bearer ${testUtil.jwts.member}`, }) .send({ - param: { - value: notPresent.param.value, - }, + value: notPresent.value, }) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) done(err); - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(id); - resJson.key.should.be.eql(bodyParamNonMutable.key); - resJson.value.should.be.eql(notPresent.param.value); - resJson.valueType.should.be.eql(notPresent.param.valueType); + resJson.key.should.be.eql(bodyNonMutable.key); + resJson.value.should.be.eql(notPresent.value); + resJson.valueType.should.be.eql(notPresent.valueType); resJson.projectId.should.be.eql(projectId); - resJson.createdBy.should.be.eql(bodyParamNonMutable.createdBy); + resJson.createdBy.should.be.eql(bodyNonMutable.createdBy); resJson.updatedBy.should.be.eql(40051331); should.exist(resJson.updatedAt); should.not.exist(resJson.deletedBy); should.not.exist(resJson.deletedAt); expectAfterUpdate(id, projectId, _.assign(estimation, { id: estimationId, - value: notPresent.param.value, - valueType: notPresent.param.valueType, - key: bodyParamNonMutable.key, + value: notPresent.value, + valueType: notPresent.valueType, + key: bodyNonMutable.key, }), 0, 0, err, done); }); }); @@ -329,7 +323,7 @@ describe('UPDATE Project Setting', () => { }); it('should return 200 for admin when value updated, calculating project estimation items', (done) => { - body.param.value = '4500'; + body.value = '4500'; models.ProjectEstimationItem.create({ projectEstimationId: estimationId, @@ -346,71 +340,67 @@ describe('UPDATE Project Setting', () => { Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - value: body.param.value, - }, + value: body.value, }) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) done(err); - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(id); - resJson.key.should.be.eql(bodyParamNonMutable.key); - resJson.value.should.be.eql(body.param.value); - resJson.valueType.should.be.eql(body.param.valueType); + resJson.key.should.be.eql(bodyNonMutable.key); + resJson.value.should.be.eql(body.value); + resJson.valueType.should.be.eql(body.valueType); resJson.projectId.should.be.eql(projectId); - resJson.createdBy.should.be.eql(bodyParamNonMutable.createdBy); + resJson.createdBy.should.be.eql(bodyNonMutable.createdBy); resJson.updatedBy.should.be.eql(40051333); // admin should.exist(resJson.updatedAt); should.not.exist(resJson.deletedBy); should.not.exist(resJson.deletedAt); expectAfterUpdate(id, projectId, _.assign(estimation, { id: estimationId, - value: body.param.value, - valueType: body.param.valueType, - key: bodyParamNonMutable.key, + value: body.value, + valueType: body.valueType, + key: bodyNonMutable.key, }), 1, 1, err, done); }); }).catch(done); }); it('should return 200, for admin, update valueType from double to percentage', (done) => { - body.param.value = '10.76'; - body.param.valueType = VALUE_TYPE.PERCENTAGE; + body.value = '10.76'; + body.valueType = VALUE_TYPE.PERCENTAGE; request(server) .patch(`/v5/projects/${projectId}/settings/${id}`) .set({ Authorization: `Bearer ${testUtil.jwts.admin}`, }) .send({ - param: { - value: body.param.value, - valueType: VALUE_TYPE.PERCENTAGE, - }, + value: body.value, + valueType: VALUE_TYPE.PERCENTAGE, }) .expect('Content-Type', /json/) .expect(200) .end((err, res) => { if (err) done(err); - const resJson = res.body.result.content; + const resJson = res.body; resJson.id.should.be.eql(id); - resJson.key.should.be.eql(bodyParamNonMutable.key); - resJson.value.should.be.eql(body.param.value); + resJson.key.should.be.eql(bodyNonMutable.key); + resJson.value.should.be.eql(body.value); resJson.valueType.should.be.eql(VALUE_TYPE.PERCENTAGE); resJson.projectId.should.be.eql(projectId); - resJson.createdBy.should.be.eql(bodyParamNonMutable.createdBy); + resJson.createdBy.should.be.eql(bodyNonMutable.createdBy); resJson.updatedBy.should.be.eql(40051333); // admin should.exist(resJson.updatedAt); should.not.exist(resJson.deletedBy); should.not.exist(resJson.deletedAt); expectAfterUpdate(id, projectId, _.assign(estimation, { id: estimationId, - value: body.param.value, - valueType: body.param.valueType, - key: bodyParamNonMutable.key, + value: body.value, + valueType: body.valueType, + key: bodyNonMutable.key, }), 1, 0, err, done); }); }); diff --git a/src/routes/projectTemplates/list.js b/src/routes/projectTemplates/list.js index 45be8e17..81d1ee44 100644 --- a/src/routes/projectTemplates/list.js +++ b/src/routes/projectTemplates/list.js @@ -10,7 +10,17 @@ const permissions = tcMiddleware.permissions; module.exports = [ permissions('projectTemplate.view'), (req, res, next) => { - util.fetchFromES('projectTemplates') + util.fetchFromES('projectTemplates', { + query: { + nested: { + path: 'projectTemplates', + query: { + match: { 'projectTemplates.disabled': false }, + }, + inner_hits: {}, + }, + }, + }, 'metadata') .then((data) => { if (data.projectTemplates.length === 0) { req.log.debug('No projectTemplate found in ES'); From 77abc2111f96d30f519f08ac9a95d85ad0ea0d59 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 22 Oct 2019 14:28:31 +0800 Subject: [PATCH 12/88] fix: set 'Access-Control-Expose-Headers' so browser can get pagination data from header --- src/util.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/util.js b/src/util.js index 2740c0dd..82989b0f 100644 --- a/src/util.js +++ b/src/util.js @@ -733,7 +733,13 @@ _.assignIn(util, { link += `, <${fullUrl}page=${nextPage}>; rel="next"`; } + // Allow browsers access pagination data in headers + let accessControlExposeHeaders = res.get('Access-Control-Expose-Headers') || ''; + accessControlExposeHeaders += accessControlExposeHeaders ? ', ' : ''; + accessControlExposeHeaders += 'X-Page, X-Per-Page, X-Total, X-Total-Pages'; + res.set({ + 'Access-Control-Expose-Headers': accessControlExposeHeaders, 'X-Page': data.page, 'X-Per-Page': data.pageSize, 'X-Total': data.count, From 203892f8685e32acc9796150447f1c541a384731 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 22 Oct 2019 14:33:56 +0800 Subject: [PATCH 13/88] fix: values of query params might be encoded when having special characters Some query params may have special characters thus got encoded client-side. So I'm reverting code which uses "decodeURIComponent" to decode such query params. TODO: we might replace custom logic for decoding **some** query params with an automatic mechanism to decode **all** query params. --- src/routes/milestoneTemplates/list.js | 2 +- src/routes/milestones/list.js | 2 +- src/routes/phases/list.js | 4 ++-- src/routes/projects/list.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/routes/milestoneTemplates/list.js b/src/routes/milestoneTemplates/list.js index 68528ef1..572b8364 100644 --- a/src/routes/milestoneTemplates/list.js +++ b/src/routes/milestoneTemplates/list.js @@ -14,7 +14,7 @@ module.exports = [ permissions('milestoneTemplate.view'), (req, res, next) => { // Parse the sort query - let sort = req.query.sort ? req.query.sort : 'order'; + let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'order'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } diff --git a/src/routes/milestones/list.js b/src/routes/milestones/list.js index ab7a4f82..cad31a5a 100644 --- a/src/routes/milestones/list.js +++ b/src/routes/milestones/list.js @@ -28,7 +28,7 @@ module.exports = [ permissions('milestone.view'), (req, res, next) => { // Parse the sort query - let sort = req.query.sort ? req.query.sort : 'order'; + let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'order'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } diff --git a/src/routes/phases/list.js b/src/routes/phases/list.js index c7697239..5370c2e3 100644 --- a/src/routes/phases/list.js +++ b/src/routes/phases/list.js @@ -20,8 +20,8 @@ module.exports = [ const projectId = _.parseInt(req.params.projectId); // Parse the fields string to determine what fields are to be returned - let fields = req.query.fields ? req.query.fields.split(',') : PHASE_ATTRIBUTES; - let sort = req.query.sort ? req.query.sort : 'startDate'; + let fields = req.query.fields ? decodeURIComponent(req.query.fields).split(',') : PHASE_ATTRIBUTES; + let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'startDate'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index 07fc837e..3a03d05c 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -531,7 +531,7 @@ module.exports = [ // handle filters let filters = _.omit(req.query, 'sort', 'perPage', 'page', 'fields'); - let sort = req.query.sort ? req.query.sort : 'createdAt'; + let sort = req.query.sort ? decodeURIComponent(req.query.sort) : 'createdAt'; if (sort && sort.indexOf(' ') === -1) { sort += ' asc'; } From 4fe4e6c14cfd07d0a239cbd008ba257b8f86b7a5 Mon Sep 17 00:00:00 2001 From: gets0ul Date: Thu, 24 Oct 2019 13:18:17 +0700 Subject: [PATCH 14/88] Add db query parameter to support getting timeline directly from database bypassing ElasticSearch. --- src/routes/timelines/get.js | 35 ++++--- src/routes/timelines/get.spec.js | 152 ++++++++++++++++++++++++------- 2 files changed, 140 insertions(+), 47 deletions(-) diff --git a/src/routes/timelines/get.js b/src/routes/timelines/get.js index d55b5af2..f5145ffa 100644 --- a/src/routes/timelines/get.js +++ b/src/routes/timelines/get.js @@ -20,8 +20,22 @@ const schema = { params: { timelineId: Joi.number().integer().positive().required(), }, + query: { + db: Joi.boolean().optional(), + }, }; +// Load the milestones +const loadMilestones = timeline => + timeline.getMilestones() + .then((milestones) => { + const loadedTimeline = _.omit(timeline.toJSON(), ['deletedAt', 'deletedBy']); + loadedTimeline.milestones = + _.map(milestones, milestone => _.omit(milestone.toJSON(), ['deletedAt', 'deletedBy'])); + + return Promise.resolve(loadedTimeline); + }); + module.exports = [ validate(schema), // Validate and get projectId from the timelineId param, and set to request params for @@ -29,27 +43,24 @@ module.exports = [ validateTimeline.validateTimelineIdParam, permissions('timeline.view'), (req, res, next) => { - eClient.get({ index: ES_TIMELINE_INDEX, + // when user query with db, bypass the elasticsearch + // and get the data directly from database + if (req.query.db) { + req.log.debug('bypass ES, gets timeline directly from database'); + return loadMilestones(req.timeline).then(timeline => res.json(timeline)); + } + return eClient.get({ index: ES_TIMELINE_INDEX, type: ES_TIMELINE_TYPE, id: req.params.timelineId, }) .then((doc) => { req.log.debug('timeline found in ES'); - res.json(doc._source); // eslint-disable-line no-underscore-dangle + return res.json(doc._source); // eslint-disable-line no-underscore-dangle }) .catch((err) => { if (err.status === 404) { req.log.debug('No timeline found in ES'); - // Load the milestones - return req.timeline.getMilestones() - .then((milestones) => { - const timeline = _.omit(req.timeline.toJSON(), ['deletedAt', 'deletedBy']); - timeline.milestones = - _.map(milestones, milestone => _.omit(milestone.toJSON(), ['deletedAt', 'deletedBy'])); - - // Write to response - return res.json(timeline); - }); + return loadMilestones(req.timeline).then(timeline => res.json(timeline)); } return next(err); }); diff --git a/src/routes/timelines/get.spec.js b/src/routes/timelines/get.spec.js index 01ac60f5..82ac3b50 100644 --- a/src/routes/timelines/get.spec.js +++ b/src/routes/timelines/get.spec.js @@ -3,6 +3,8 @@ */ import chai from 'chai'; import request from 'supertest'; +import config from 'config'; +import _ from 'lodash'; import models from '../../models'; import server from '../../app'; @@ -10,6 +12,42 @@ import testUtil from '../../tests/util'; const should = chai.should(); +const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName'); +const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType'); + +const timelines = [ + { + name: 'name 1', + description: 'description 1', + startDate: '2018-05-11T00:00:00.000Z', + endDate: '2018-05-12T00:00:00.000Z', + reference: 'project', + referenceId: 1, + createdBy: 1, + updatedBy: 1, + }, + { + name: 'name 2', + description: 'description 2', + startDate: '2018-05-12T00:00:00.000Z', + endDate: '2018-05-13T00:00:00.000Z', + reference: 'phase', + referenceId: 1, + createdBy: 1, + updatedBy: 1, + }, + { + name: 'name 3', + description: 'description 3', + startDate: '2018-05-13T00:00:00.000Z', + endDate: '2018-05-14T00:00:00.000Z', + reference: 'phase', + referenceId: 1, + createdBy: 1, + updatedBy: 1, + deletedAt: '2018-05-14T00:00:00.000Z', + }, +]; const milestones = [ { id: 1, @@ -143,41 +181,37 @@ describe('GET timeline', () => { ])) .then(() => // Create timelines - models.Timeline.bulkCreate([ - { - name: 'name 1', - description: 'description 1', - startDate: '2018-05-11T00:00:00.000Z', - endDate: '2018-05-12T00:00:00.000Z', - reference: 'project', - referenceId: 1, - createdBy: 1, - updatedBy: 1, - }, - { - name: 'name 2', - description: 'description 2', - startDate: '2018-05-12T00:00:00.000Z', - endDate: '2018-05-13T00:00:00.000Z', - reference: 'phase', - referenceId: 1, - createdBy: 1, - updatedBy: 1, - }, - { - name: 'name 3', - description: 'description 3', - startDate: '2018-05-13T00:00:00.000Z', - endDate: '2018-05-14T00:00:00.000Z', - reference: 'phase', - referenceId: 1, - createdBy: 1, - updatedBy: 1, - deletedAt: '2018-05-14T00:00:00.000Z', - }, - ])) - .then(() => models.Milestone.bulkCreate(milestones)) - .then(() => done()); + // Create timelines + models.Timeline.bulkCreate(timelines, { returning: true }) + .then(createdTimelines => ( + // create milestones after timelines + models.Milestone.bulkCreate(milestones)) + .then(createdMilestones => [createdTimelines, createdMilestones]), + ), + ).then(([createdTimelines, createdMilestones]) => + // Index to ES + Promise.all(_.map(createdTimelines, async (createdTimeline) => { + const timelineJson = _.omit(createdTimeline.toJSON(), 'deletedAt', 'deletedBy'); + timelineJson.projectId = createdTimeline.id !== 3 ? 1 : 2; + if (timelineJson.id === 1) { + timelineJson.milestones = _.map( + createdMilestones, + cm => _.omit(cm.toJSON(), 'deletedAt', 'deletedBy'), + ); + } else if (timelineJson.id === 2) { + timelineJson.description = 'from ES'; + } + + await server.services.es.index({ + index: ES_TIMELINE_INDEX, + type: ES_TIMELINE_TYPE, + id: timelineJson.id, + body: timelineJson, + }); + })) + .then(() => { + done(); + })); }); }); }); @@ -316,5 +350,53 @@ describe('GET timeline', () => { }) .expect(200, done); }); + + it('should return data from ES when db param is not set', (done) => { + request(server) + .get('/v5/timelines/2') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.id.should.be.eql(2); + resJson.name.should.be.eql('name 2'); + resJson.description.should.be.eql('from ES'); + + resJson.startDate.should.be.eql('2018-05-12T00:00:00.000Z'); + resJson.endDate.should.be.eql('2018-05-13T00:00:00.000Z'); + resJson.reference.should.be.eql('phase'); + resJson.referenceId.should.be.eql(1); + + resJson.createdBy.should.be.eql(1); + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(1); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); + + should.not.exist(resJson.milestones); + + done(); + }); + }); + + it('should return data from DB without calling ES when db param is set', (done) => { + request(server) + .get('/v5/timelines/2?db=true') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + resJson.id.should.be.eql(2); + resJson.name.should.be.eql('name 2'); + resJson.description.should.be.eql('description 2'); + + done(); + }); + }); }); }); From 038b0a034cabe1e160c453b8da58a5b841d0e90e Mon Sep 17 00:00:00 2001 From: gets0ul Date: Thu, 24 Oct 2019 13:22:45 +0700 Subject: [PATCH 15/88] - support filtering orgConfigs by multiple orgId - filter orgConfigs data from ES - add unit tests for the changes --- src/routes/orgConfig/list.js | 56 +++++++++---- src/routes/orgConfig/list.spec.js | 134 +++++++++++++++++++++++++++--- 2 files changed, 164 insertions(+), 26 deletions(-) diff --git a/src/routes/orgConfig/list.js b/src/routes/orgConfig/list.js index b77b3dc3..6e5fb747 100644 --- a/src/routes/orgConfig/list.js +++ b/src/routes/orgConfig/list.js @@ -3,6 +3,7 @@ */ import validate from 'express-validation'; import Joi from 'joi'; +import _ from 'lodash'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; @@ -20,24 +21,51 @@ module.exports = [ validate(schema), permissions('orgConfig.view'), (req, res, next) => { - util.fetchFromES('orgConfigs') - .then((data) => { - // handle filters - const filters = req.query; - // Throw error if orgId is not present in filter - if (!filters.orgId) { - next(util.buildApiError('Missing filter orgId', 400)); - } - if (!util.isValidFilter(filters, ['orgId', 'configName'])) { - util.handleError('Invalid filters', null, req, next); - } - req.log.debug(filters); + // handle filters + const filters = req.query; + // Throw error if orgId is not present in filter + if (!filters.orgId) { + next(util.buildApiError('Missing filter orgId', 400)); + } + if (!util.isValidFilter(filters, ['orgId', 'configName'])) { + util.handleError('Invalid filters', null, req, next); + } + req.log.debug(filters); + const orgIds = filters.orgId.split(','); + // build filter query for ES + const must = [{ + terms: { + 'orgConfigs.orgId': orgIds, + }, + }]; + if (filters.configName) { + must.push({ + term: { + 'orgConfigs.configName': filters.configName, + }, + }); + } + + util.fetchFromES('orgConfigs', { + query: { + nested: { + path: 'orgConfigs', + query: { + bool: { + must, + }, + }, + inner_hits: {}, + }, + }, + }, 'metadata') + .then((data) => { if (data.orgConfigs.length === 0) { req.log.debug('No orgConfig found in ES'); // Get all organization config - const where = filters || {}; + const where = filters ? _.assign({}, filters, { orgId: { $in: orgIds } }) : {}; models.OrgConfig.findAll({ where, attributes: { exclude: ['deletedAt', 'deletedBy'] }, @@ -49,7 +77,7 @@ module.exports = [ .catch(next); } else { req.log.debug('orgConfigs found in ES'); - res.json(data.orgConfigs); + res.json(data.orgConfigs.hits.hits.map(hit => hit._source)); // eslint-disable-line no-underscore-dangle } }); }, diff --git a/src/routes/orgConfig/list.spec.js b/src/routes/orgConfig/list.spec.js index 69317422..498f1187 100644 --- a/src/routes/orgConfig/list.spec.js +++ b/src/routes/orgConfig/list.spec.js @@ -3,6 +3,8 @@ */ import chai from 'chai'; import request from 'supertest'; +import config from 'config'; +import _ from 'lodash'; import models from '../../models'; import server from '../../app'; @@ -10,6 +12,21 @@ import testUtil from '../../tests/util'; const should = chai.should(); +const ES_ORGCONFIG_INDEX = config.get('elasticsearchConfig.metadataIndexName'); +const ES_ORGCONFIG_TYPE = config.get('elasticsearchConfig.metadataDocType'); + +const validateOrgConfig = (resJson, orgConfig) => { + resJson.id.should.be.eql(orgConfig.id); + resJson.orgId.should.be.eql(orgConfig.orgId); + resJson.configName.should.be.eql(orgConfig.configName); + resJson.configValue.should.be.eql(orgConfig.configValue); + should.exist(resJson.createdAt); + resJson.updatedBy.should.be.eql(orgConfig.updatedBy); + should.exist(resJson.updatedAt); + should.not.exist(resJson.deletedBy); + should.not.exist(resJson.deletedAt); +}; + describe('LIST organization config', () => { const orgConfigPath = '/v5/projects/metadata/orgConfig'; const configs = [ @@ -23,17 +40,50 @@ describe('LIST organization config', () => { }, { id: 2, - orgId: 'ORG1', + orgId: 'ORG2', configName: 'project_catalog_url', configValue: '/projects/2', createdBy: 1, updatedBy: 1, }, + { + id: 3, + orgId: 'ORG3', + configName: 'project_catalog_url', + configValue: '/projects/3', + createdBy: 1, + updatedBy: 1, + }, + { + id: 4, + orgId: 'ORG4', + configName: 'project_catalog_url', + configValue: '/projects/4', + createdBy: 1, + updatedBy: 1, + }, ]; beforeEach((done) => { testUtil.clearDb() - .then(() => models.OrgConfig.bulkCreate(configs).then(() => done())); + .then(() => models.OrgConfig.bulkCreate(configs, { returning: true }), + ).then(async (createdConfigs) => { + // Index to ES only orgConfigs with id: 3 and 4 + const indexedConfigs = _(createdConfigs).filter(createdConfig => createdConfig.toJSON().id > 2) + .map((filteredConfig) => { + const orgConfigJson = _.omit(filteredConfig.toJSON(), 'deletedAt', 'deletedBy'); + return orgConfigJson; + }).value(); + + await server.services.es.index({ + index: ES_ORGCONFIG_INDEX, + type: ES_ORGCONFIG_TYPE, + body: { + orgConfigs: indexedConfigs, + }, + }); + done(); + }); }); after((done) => { testUtil.clearDb(done); @@ -48,19 +98,79 @@ describe('LIST organization config', () => { }) .expect(200) .end((err, res) => { - const config = configs[0]; + const config1 = configs[0]; + + const resJson = res.body; + resJson.should.have.length(1); + validateOrgConfig(resJson[0], config1); + + done(); + }); + }); + + it('should return 200 for admin with filter (ES)', (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[2].orgId}&configName=${configs[2].configName}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const config3 = configs[2]; const resJson = res.body; resJson.should.have.length(1); - resJson[0].id.should.be.eql(config.id); - resJson[0].orgId.should.be.eql(config.orgId); - resJson[0].configName.should.be.eql(config.configName); - resJson[0].configValue.should.be.eql(config.configValue); - should.exist(resJson[0].createdAt); - resJson[0].updatedBy.should.be.eql(config.updatedBy); - should.exist(resJson[0].updatedAt); - should.not.exist(resJson[0].deletedBy); - should.not.exist(resJson[0].deletedAt); + validateOrgConfig(resJson[0], config3); + + done(); + }); + }); + + it('should return 200 for admin and filter by multiple orgId (DB)', (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[0].orgId},${configs[1].orgId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const config1 = configs[0]; + const config2 = configs[1]; + + const resJson = res.body; + resJson.should.have.length(2); + resJson.forEach((result) => { + if (result.id === 1) { + validateOrgConfig(result, config1); + } else { + validateOrgConfig(result, config2); + } + }); + + done(); + }); + }); + + it('should return 200 for admin and filter by multiple orgId (ES)', (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[2].orgId},${configs[3].orgId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const config3 = configs[2]; + const config4 = configs[3]; + + const resJson = res.body; + resJson.should.have.length(2); + resJson.forEach((result) => { + if (result.id === 3) { + validateOrgConfig(result, config3); + } else { + validateOrgConfig(result, config4); + } + }); done(); }); From 7b5f8bd53148c08f8eabcecfd1b368f4ce051e4b Mon Sep 17 00:00:00 2001 From: Sumit Daga Date: Fri, 25 Oct 2019 14:56:28 +0530 Subject: [PATCH 16/88] F2F, update postman --- docs/Project API.postman_collection.json | 7509 ++++++++++++++++------ 1 file changed, 5365 insertions(+), 2144 deletions(-) diff --git a/docs/Project API.postman_collection.json b/docs/Project API.postman_collection.json index cb1e0c8e..449b221f 100644 --- a/docs/Project API.postman_collection.json +++ b/docs/Project API.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "90ff8f9b-5661-4642-8b8b-84aa4b581706", + "_postman_id": "bd828203-8def-4648-bb54-e509c177ec71", "name": "Project API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -244,6 +244,7 @@ "response": [] } ], + "protocolProfileBehavior": {}, "_postman_isSubFolder": true }, { @@ -435,7 +436,8 @@ }, "response": [] } - ] + ], + "protocolProfileBehavior": {} }, { "name": "Project With TemplateId issue", @@ -515,7 +517,8 @@ }, "response": [] } - ] + ], + "protocolProfileBehavior": {} }, { "name": "Project Members", @@ -1024,7 +1027,8 @@ }, "response": [] } - ] + ], + "protocolProfileBehavior": {} }, { "name": "Project Members Invites", @@ -1223,7 +1227,8 @@ ] } } - ] + ], + "protocolProfileBehavior": {} }, { "name": "Projects", @@ -1421,6 +1426,177 @@ }, "response": [] }, + { + "name": "List projects DB", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/db", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "db" + ] + }, + "description": "List all the project with no filter. Default sort and limits are applied." + }, + "response": [] + }, + { + "name": "List projects DB with limit and offset", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/db?limit=1&offset=1", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "db" + ], + "query": [ + { + "key": "limit", + "value": "1" + }, + { + "key": "offset", + "value": "1" + } + ] + }, + "description": "List all the project with no filter. Limit of 1 and offset of 1 is applied" + }, + "response": [] + }, + { + "name": "List projects DB with filters applied", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/db?filter=type%3Dgeneric", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "db" + ], + "query": [ + { + "key": "filter", + "value": "type%3Dgeneric" + } + ] + }, + "description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable" + }, + "response": [] + }, + { + "name": "List projects DB with sort applied", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/db?sort=type%20desc", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "db" + ], + "query": [ + { + "key": "sort", + "value": "type%20desc" + } + ] + }, + "description": "List all the project with custom sort and no filter. Default sort and limits are applied. The sort string has to be url encoded. Sort is of type `key asc|desc`" + }, + "response": [] + }, + { + "name": "List projects DB and return specific fields", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/db?fields=id,name,description", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "db" + ], + "query": [ + { + "key": "fields", + "value": "id,name,description" + } + ] + }, + "description": "List all the project with no filter. Default sort and limits are applied. The fields to return is specified as comma separated list. Only those fields should be returned." + }, + "response": [] + }, + { + "name": "List projects DB with copilot token", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/db", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "db" + ] + } + }, + "response": [] + }, { "name": "List projects", "request": { @@ -2157,15 +2333,16 @@ "response": [] } ], - "description": "Requests for all things projects." + "description": "Requests for all things projects.", + "protocolProfileBehavior": {} }, { - "name": "EventHandling and Integration with Direct Project API", + "name": "Workstream", "item": [ { - "name": "mock direct projects", + "name": "Create workstream without payload", "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Authorization", @@ -2176,25 +2353,27 @@ "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n\t\n}" + }, "url": { - "raw": "https://api.topcoder-dev.com/v3/direct/projects", - "protocol": "https", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams", "host": [ - "api", - "topcoder-dev", - "com" + "{{api-url}}" ], "path": [ - "v3", - "direct", - "projects" + "projects", + "{{projectId}}", + "workstreams" ] - } + }, + "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code." }, "response": [] }, { - "name": " Create direct project when a new project is successfully created", + "name": "Create workstream without valid name", "request": { "method": "POST", "header": [ @@ -2209,22 +2388,40 @@ ], "body": { "mode": "raw", - "raw": "{\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }" + "raw": "{\n}" }, "url": { - "raw": "{{api-url}}/projects", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams", "host": [ "{{api-url}}" ], "path": [ - "projects" + "projects", + "{{projectId}}", + "workstreams" ] - } + }, + "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." }, "response": [] }, { - "name": "Response error from direct project service", + "name": "Create workstream with valid values", + "event": [ + { + "listen": "test", + "script": { + "id": "15506f7a-77d3-46cb-9b37-41015ffbfdbc", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"workStreamId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -2239,30 +2436,31 @@ ], "body": { "mode": "raw", - "raw": "{\n\n \"role\": \"copilot\"\n}" + "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/members", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "members" + "workstreams" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": " Add co-pilot when a co-pilot is added to a project", + "name": "Create workstream by inactive user", "request": { "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer userId_{{inactive-userId}}" }, { "key": "Content-Type", @@ -2271,98 +2469,82 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"role\": \"copilot\"\n}" + "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/members", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "members" + "workstreams" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "remove copilot from direct project when editing project member role", + "name": "Get workstream by id", "request": { - "method": "PATCH", + "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": " {\n \"role\": \"customer\",\n \"isPrimary\": true\n } " - }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "members", - "{{memberId}}" + "workstreams", + "{{workStreamId}}" ] - } + }, + "description": "Get a project by id. project members and attachments should also be returned." }, "response": [] }, { - "name": " Sync billing account id with direct", + "name": "List workstreams", "request": { - "method": "PATCH", + "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }" - }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams", "host": [ "{{api-url}}" ], "path": [ "projects", - "{{projectId}}" + "{{projectId}}", + "workstreams" ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] }, { - "name": "Delete co-pilot when a co-pilot is removed from a project", + "name": "DELETE workstream by id", "request": { "method": "DELETE", "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" } ], "body": { @@ -2370,64 +2552,64 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "members", - "{{memberId}}" + "workstreams", + "{{workStreamId}}" ] - } + }, + "description": "Delete a project by id" }, "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "ef96ac6a-0fc0-4a64-a4fe-5390e17afe67", - "type": "text/javascript", - "exec": [ - "" - ] - } }, { - "listen": "test", - "script": { - "id": "12f9d794-0872-4058-aafa-77b89e72025b", - "type": "text/javascript", - "exec": [ - "" - ] - } + "name": "Update workstream", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"name\": \"test project - updated\"\n}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "workstreams", + "{{workStreamId}}" + ] + }, + "description": "Update the project name. Name should be updated successfully." + }, + "response": [] } - ] + ], + "description": "Requests for all things projects.", + "protocolProfileBehavior": {} }, { - "name": "Project Phase", + "name": "Work", "item": [ { - "name": "Create Phase", - "event": [ - { - "listen": "test", - "script": { - "id": "7050133a-b934-4faf-8101-d2e80b5c0710", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"phaseId\", pm.response.json().id);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create work without payload", "request": { "method": "POST", "header": [ @@ -2442,39 +2624,27 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t}\n}" + "raw": "{\n\t\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases" + "workstreams", + "{{workStreamId}}", + "works" ] - } + }, + "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code." }, "response": [] }, { - "name": "Create Phase with order", - "event": [ - { - "listen": "test", - "script": { - "id": "2f771afe-7b4e-4260-b04d-324e880eb61b", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"phaseId\", pm.response.json().id);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create work without valid name", "request": { "method": "POST", "header": [ @@ -2489,33 +2659,36 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t},\n\t\"order\": 1\n}" + "raw": "{\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases" + "workstreams", + "{{workStreamId}}", + "works" ] - } + }, + "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." }, "response": [] }, { - "name": "Create Phase with productTemplateId", + "name": "Create work with valid values", "event": [ { "listen": "test", "script": { - "id": "8415ad98-b3f6-4330-88b6-e1830da2e4f9", + "id": "34d06fac-76f6-47b8-9b70-6e9c558e2bf1", "exec": [ "pm.test(\"Status code is 201\", function () {", " pm.response.to.have.status(201);", - " pm.environment.set(\"phaseId\", pm.response.json().id);", + " pm.environment.set(\"workId\", pm.response.json().id);", "});" ], "type": "text/javascript" @@ -2536,216 +2709,250 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t},\n\t\"order\": 1,\n\t\"productTemplateId\": {{productTemplateId}}\n}" + "raw": "{\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-16T00:00:00\",\n\t\t\"endDate\": \"2018-05-17T00:00:00\",\n\t\t\"order\": 1,\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t}\n\t}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases" + "workstreams", + "{{workStreamId}}", + "works" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "List Phase", + "name": "Create work - 403", "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer {{jwt-token-member-40051331}}" }, { "key": "Content-Type", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"name\": \"test project phase\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"order\": 2,\n\t\t\"budget\": 20,\n\t\t\"details\": {\n\t\t\t\"aDetails\": \"a details\"\n\t\t}\n\t}" + }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases" + "workstreams", + "{{workStreamId}}", + "works" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "List Phase with fields", + "name": "Create work by inactive user", "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer userId_{{inactive-userId}}" }, { "key": "Content-Type", "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n}" + }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases?fields=status,name,budget", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases" - ], - "query": [ - { - "key": "fields", - "value": "status,name,budget" - } + "workstreams", + "{{workStreamId}}", + "works" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "List Phase with sort", + "name": "Get work by id", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" } ], "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases?sort=status desc", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases" - ], - "query": [ - { - "key": "sort", - "value": "status desc" - } + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}" ] - } + }, + "description": "Get a project by id. project members and attachments should also be returned." }, "response": [] }, { - "name": "List Phase with sort by order", + "name": "List works", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" - }, + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "workstreams", + "{{workStreamId}}", + "works" + ] + }, + "description": "List all the project with no filter. Default sort and limits are applied." + }, + "response": [] + }, + { + "name": "List works - sort", + "request": { + "method": "GET", + "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token}}" } ], "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases?sort=order desc", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works?sort=startDate desc", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases" + "workstreams", + "{{workStreamId}}", + "works" ], "query": [ { "key": "sort", - "value": "order desc" + "value": "startDate desc" } ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] }, { - "name": "Get Phase", + "name": "List works - fields", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" } ], "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works?fields=status,name,budget", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases", - "{{phaseId}}" + "workstreams", + "{{workStreamId}}", + "works" + ], + "query": [ + { + "key": "fields", + "value": "status,name,budget" + } ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] }, { - "name": "Update Phase", + "name": "DELETE work by id", "request": { - "method": "PATCH", + "method": "DELETE", "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t}\n}" + "raw": "" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases", - "{{phaseId}}" + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}" ] - } + }, + "description": "Delete a project by id" }, "response": [] }, { - "name": "Update Phase with order", + "name": "Update work", "request": { "method": "PATCH", "header": [ @@ -2760,31 +2967,34 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t},\n\t\"order\": 1\n}" + "raw": "{\n\t\t\"name\": \"test project phase 11\",\n\t\t\"status\": \"active\",\n\t\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\t\"budget\": 24,\n\t\t\"order\": 2\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases", - "{{phaseId}}" + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}" ] - } + }, + "description": "Update the project name. Name should be updated successfully." }, "response": [] }, { - "name": "Delete Phase", + "name": "Update work 403", "request": { - "method": "DELETE", + "method": "PATCH", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer {{jwt-token-copilot-40051332}}" }, { "key": "Content-Type", @@ -2793,45 +3003,33 @@ ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\t\"name\": \"test project - updated\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases", - "{{phaseId}}" + "workstreams", + "{{workStreamId}}" ] - } + }, + "description": "Update the project name. If user don't have permission to the project than it should return 403." }, "response": [] } - ] + ], + "description": "Requests for all things projects.", + "protocolProfileBehavior": {} }, { - "name": "Phase Products", + "name": "Work Item", "item": [ { - "name": "Create Phase Product", - "event": [ - { - "listen": "test", - "script": { - "id": "77f089b3-cbe6-4fb4-b54f-2a52d138a050", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"phaseProductId\", pm.response.json().id);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create work item without payload", "request": { "method": "POST", "header": [ @@ -2846,85 +3044,122 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"name\": \"test phase product\",\n\t\"type\": \"type 1\",\n\t\"estimatedPrice\": 10\n}" + "raw": "{\n\t\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products", + "raw": "{{api-url}}/projects/workstreams/{{workStreamId}}/works/{{workId}}/workitems", "host": [ "{{api-url}}" ], "path": [ "projects", - "{{projectId}}", - "phases", - "{{phaseId}}", - "products" + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}", + "workitems" ] - } + }, + "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code." }, "response": [] }, { - "name": "List Phase Products", + "name": "Create work item without valid name", "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n}" + }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases", - "{{phaseId}}", - "products" + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}", + "workitems" ] - } + }, + "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." }, "response": [] }, { - "name": "Get Phase Product", + "name": "Create work item with valid values", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"itemId\", pm.response.json().id);", + "});" + ] + } + } + ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"name\": \"work item - phase product\",\n\t\t\"type\": \"type 1\",\n\t\t\"estimatedPrice\": 12\n}" + }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases", - "{{phaseId}}", - "products", - "{{phaseProductId}}" + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}", + "workitems" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "Update Phase Product", + "name": "Create work item by inactive user", "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer userId_{{inactive-userId}}" }, { "key": "Content-Type", @@ -2933,27 +3168,88 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"name\": \"test phase product xxx\",\n\t\"type\": \"type 2\",\n\t\"templateId\": 10,\n\t\"estimatedPrice\": 1.234567,\n\t\"actualPrice\": 2.34567,\n\t\"details\": {\n\t\t\"message\": \"this is a JSON type. You can use any json\"\n\t}\n}" + "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"description\": \"Hello I am a test project\",\n\t\t\"type\": \"generic\"\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases", - "{{phaseId}}", - "products", - "{{phaseProductId}}" + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}", + "workitems" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "Delete Phase Product", + "name": "Get work item by id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems/{{itemId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}", + "workitems", + "{{itemId}}" + ] + }, + "description": "Get a project by id. project members and attachments should also be returned." + }, + "response": [] + }, + { + "name": "List work items", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}", + "workitems" + ] + }, + "description": "List all the project with no filter. Default sort and limits are applied." + }, + "response": [] + }, + { + "name": "DELETE work item by id", "request": { "method": "DELETE", "header": [ @@ -2967,85 +3263,147 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems/{{itemId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "phases", - "{{phaseId}}", - "products", - "{{phaseProductId}}" + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}", + "workitems", + "{{itemId}}" ] - } + }, + "description": "Delete a project by id" + }, + "response": [] + }, + { + "name": "Update work item", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"name\": \"work item - updated\"\n}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems/{{itemId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "workstreams", + "{{workStreamId}}", + "works", + "{{workId}}", + "workitems", + "{{itemId}}" + ] + }, + "description": "Update the project name. Name should be updated successfully." }, "response": [] } - ] + ], + "description": "Requests for all things projects.", + "protocolProfileBehavior": {} }, { - "name": "Project Templates", + "name": "Work Management Permission", "item": [ { - "name": "Create project template", - "event": [ - { - "listen": "test", - "script": { - "id": "2f79c07b-8076-4715-abf7-1d6903df444f", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"projectTemplateId\", pm.response.json().id);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Create work management permission without payload", "request": { "method": "POST", "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, { "key": "Content-Type", "value": "application/json" - }, + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\n}" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/workManagementPermission", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "workManagementPermission" + ] + }, + "description": "Request body is mandatory while creating project. If invalid request body is supplied this should return 422 status code." + }, + "response": [] + }, + { + "name": "Create work management permission without valid name", + "request": { + "method": "POST", + "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"scope\":{\r\n \"scope1\":\"scope 1\"\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\"\r\n }\r\n }" + "raw": "{\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/workManagementPermission", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates" + "workManagementPermission" ] - } + }, + "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." }, "response": [] }, { - "name": "Create project template with form, priceConfig, planConfig", + "name": "Create work management permission with valid values", "event": [ { "listen": "test", "script": { - "id": "4c442ea3-0834-4a30-8044-a4e94fd4ea2d", + "id": "305f3d68-6e9f-4c4d-b031-7487986a93e2", "exec": [ "pm.test(\"Status code is 201\", function () {", " pm.response.to.have.status(201);", - " pm.environment.set(\"projectTemplateId\", pm.response.json().id);", + " pm.environment.set(\"workManagementPermissionId\", pm.response.json().id);", "});" ], "type": "text/javascript" @@ -3055,44 +3413,45 @@ "request": { "method": "POST", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n }\r\n }" + "raw": "{\n \"policy\": \"work.new.create\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 1\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/workManagementPermission", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates" + "workManagementPermission" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "Create project template with only form key", + "name": "Create work management permission with valid values", "event": [ { "listen": "test", "script": { - "id": "7d0ae3ca-fe2d-40eb-b5c8-9b03955babec", + "id": "305f3d68-6e9f-4c4d-b031-7487986a93e2", "exec": [ "pm.test(\"Status code is 201\", function () {", " pm.response.to.have.status(201);", - " pm.environment.set(\"projectTemplateId\", pm.response.json().id);", + " pm.environment.set(\"workManagementPermissionId\", pm.response.json().id);", "});" ], "type": "text/javascript" @@ -3102,265 +3461,250 @@ "request": { "method": "POST", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\"\r\n }\r\n }" + "raw": "{\n \"policy\": \"work.new.create\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 2\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/workManagementPermission", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates" + "workManagementPermission" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "Create project template with wrong form key", + "name": "Create work management permission by inactive user", "request": { "method": "POST", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer userId_{{inactive-userId}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"wrong-key\"\r\n }\r\n }" + "raw": "{\n \"policy\": \"work.create\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 1\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/workManagementPermission", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates" + "workManagementPermission" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "Create project template with wrong model version", + "name": "Get work management permission by id", "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n }\r\n }" - }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/workManagementPermission/{{workManagementPermissionId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates" + "workManagementPermission", + "{{workManagementPermissionId}}" ] - } + }, + "description": "Get a project by id. project members and attachments should also be returned." }, "response": [] }, { - "name": "List project templates", + "name": "List work management permissions - filter required", "request": { "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" } ], "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates", + "raw": "{{api-url}}/projects/metadata/workManagementPermission", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates" + "workManagementPermission" ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] }, { - "name": "Get project template", + "name": "List work management permissions - missing filter", "request": { "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" } ], "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", + "raw": "{{api-url}}/projects/metadata/workManagementPermission?filter=template", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates", - "{{projectTemplateId}}" + "workManagementPermission" + ], + "query": [ + { + "key": "filter", + "value": "template" + } ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] }, { - "name": "Upgrade a project template with from, priceConfig, planConfig", + "name": "List work management permissions - invalid filter", "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 2\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"qa\",\t\r\n \t \"version\": 2\t\r\n }\r\n }" - }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", + "raw": "{{api-url}}/projects/metadata/workManagementPermission?filter=invalid%3D2%26projectTemplateId%3D1", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates", - "{{projectTemplateId}}", - "upgrade" + "workManagementPermission" + ], + "query": [ + { + "key": "filter", + "value": "invalid%3D2%26projectTemplateId%3D1" + } ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] }, { - "name": "Upgrade a project template with wrong model version", + "name": "List work management permissions", "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n }\r\n }" - }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", + "raw": "{{api-url}}/projects/metadata/workManagementPermission?filter=projectTemplateId%3D1", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates", - "{{projectTemplateId}}", - "upgrade" + "workManagementPermission" + ], + "query": [ + { + "key": "filter", + "value": "projectTemplateId%3D1" + } ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] }, { - "name": "Upgrade a project template without define form, priceConfig, planConfig", + "name": "Update work management permission", "request": { - "method": "POST", + "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n}" + "raw": "{\n \"policy\": \"work.new.delete\",\n \"permission\": {\n \"allowRule\": {\n \"projectRoles\": [\"customer\", \"copilot\"],\n \"topcoderRoles\": [\"Connect Manager\", \"Connect Admin\", \"administrator\"]\n },\n \"denyRule\": { \"projectRoles\": [\"copilot\"] }\n },\n \"projectTemplateId\": 1\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", + "raw": "{{api-url}}/projects/metadata/workManagementPermission/{{workManagementPermissionId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates", - "{{projectTemplateId}}", - "upgrade" + "workManagementPermission", + "{{workManagementPermissionId}}" ] - } + }, + "description": "Update the project name. Name should be updated successfully." }, "response": [] }, { - "name": "Update project template", + "name": "Delete work management permission by id", "request": { - "method": "PATCH", + "method": "DELETE", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" @@ -3368,882 +3712,769 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" + "raw": "" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", + "raw": "{{api-url}}/projects/metadata/workManagementPermission/{{workManagementPermissionId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "projectTemplates", - "{{projectTemplateId}}" + "workManagementPermission", + "{{workManagementPermissionId}}" ] - } + }, + "description": "Delete a project by id" }, "response": [] - }, + } + ], + "description": "Requests for all things projects.", + "protocolProfileBehavior": {} + }, + { + "name": "Permissions", + "item": [ { - "name": "Update project template with define form, priceConfig, planConfig", + "name": "Get permissions - 404", "request": { - "method": "PATCH", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n }\r\n }" - }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", + "raw": "{{api-url}}/projects/9999/permissions", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "projectTemplates", - "{{projectTemplateId}}" + "9999", + "permissions" ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] }, { - "name": "Update project template with wrong form, priceConfig, planConfig", + "name": "Create project invite for customer with valid values", "request": { - "method": "PATCH", + "method": "POST", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n\t\"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n }\r\n }" + "raw": "{\n\t\t\"userIds\": [40051331],\n\t\t\"role\": \"customer\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", + "raw": "{{api-url}}/projects/{{projectId}}/members/invite", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "projectTemplates", - "{{projectTemplateId}}" + "{{projectId}}", + "members", + "invite" ] - } + }, + "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": "Delete project template", + "name": "Update project invite to accept invite", "request": { - "method": "DELETE", + "method": "PUT", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token-member-40051331}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" + "raw": "{\n\t\t\"userId\": 40051331,\n\t\t\"status\": \"accepted\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", + "raw": "{{api-url}}/projects/{{projectId}}/members/invite", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "projectTemplates", - "{{projectTemplateId}}" + "{{projectId}}", + "members", + "invite" ] - } + }, + "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": "Product Templates", - "item": [ + }, { - "name": "Create product template", - "event": [ - { - "listen": "test", - "script": { - "id": "b5aaf185-6026-4b58-b9b8-56616109cd5a", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"productTemplateId\", pm.response.json().id);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Get permissions", "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"{{productCategoryId}}\",\r\n \"subCategory\": \"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"template\": {\r\n \"template1\": {\r\n \"name\": \"template 1\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1\"\r\n },\r\n \"others\": [\"others 11\", \"others 12\"]\r\n },\r\n \"template2\": {\r\n \"name\": \"template 2\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 2\"\r\n },\r\n \"others\": [\"others 21\", \"others 22\"]\r\n }\r\n }\r\n }" - }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/{{projectId}}/permissions", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates" + "{{projectId}}", + "permissions" ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] }, { - "name": "Create product template with form", - "event": [ - { - "listen": "test", - "script": { - "id": "d5a2af2e-97d2-415c-a533-1d52dd4003c7", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"productTemplateId\", pm.response.json().id);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Get permissions - manager", "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer {{jwt-token-manager-40051334}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1\r\n\t}\r\n }" - }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/{{projectId}}/permissions", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates" + "{{projectId}}", + "permissions" ] - } + }, + "description": "List all the project with no filter. Default sort and limits are applied." }, "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "5197d1ec-429c-4a6f-9e9c-3ec3cd6f292a", + "type": "text/javascript", + "exec": [ + "" + ] + } }, { - "name": "Create product template with wrong form key", + "listen": "test", + "script": { + "id": "cc0cbbf1-54d1-481f-b8fa-a6dc4c80e993", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "WorkManagementForTemplate", + "item": [ + { + "name": "Create workstream with valid values", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"workStreamId1\", pm.response.json().id);", + "});" + ] + } + } + ], "request": { "method": "POST", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"wrong-key\"\r\n\t}\r\n }" + "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates" + "{{projectId}}", + "workstreams" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "Create product template with wrong model version", + "name": "Create project invite for customer with valid values", "request": { "method": "POST", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1123\r\n\t}\r\n }" + "raw": "{\n\t\t\"userIds\": [40051331],\n\t\t\"role\": \"customer\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/{{projectId}}/members/invite", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates" + "{{projectId}}", + "members", + "invite" ] - } + }, + "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": "List product templates", + "name": "Update project invite to accept invite", "request": { - "method": "GET", + "method": "PUT", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token-member-40051331}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "key": "Content-Type", + "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"userId\": 40051331,\n\t\t\"status\": \"accepted\"\n}" + }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates", + "raw": "{{api-url}}/projects/{{projectId}}/members/invite", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates" + "{{projectId}}", + "members", + "invite" ] - } + }, + "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": "Get product template", + "name": "Update work stream - no match allowRule", "request": { - "method": "GET", + "method": "PATCH", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "key": "Content-Type", + "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"name\": \"work item - updated\"\n}" + }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId1}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates", - "{{productTemplateId}}" + "{{projectId}}", + "workstreams", + "{{workStreamId1}}" ] - } + }, + "description": "Update the project name. Name should be updated successfully." }, "response": [] }, { - "name": "Update product template", + "name": "Update work stream - allow access", "request": { "method": "PATCH", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token-connectAdmin-40051336}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"productKey\":\"new productKey\",\r\n \"category\":\"key1\",\r\n \"icon\":\"http://example.com/icon-new.ico\",\r\n \"brief\": \"new brief\",\r\n \"details\": \"new details\",\r\n \"aliases\":{\r\n \"alias1\":\"scope 1\",\r\n \"alias2\": [\"a\"]\r\n },\r\n \"template\":{\r\n \"template1\":\"template 1\",\r\n \"template2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" + "raw": "{\n\t\t\"name\": \"work item - updated\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId1}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates", - "{{productTemplateId}}" + "{{projectId}}", + "workstreams", + "{{workStreamId1}}" ] - } + }, + "description": "Update the project name. Name should be updated successfully." }, "response": [] }, { - "name": "Delete product template", + "name": "Update work stream - allow access with ProjectRoles", "request": { - "method": "DELETE", + "method": "PATCH", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token-member-40051331}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\t\t\"name\": \"work item - updated\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}", + "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId1}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates", - "{{productTemplateId}}" + "{{projectId}}", + "workstreams", + "{{workStreamId1}}" ] - } + }, + "description": "Update the project name. Name should be updated successfully." }, "response": [] }, { - "name": "Upgrade a product template with form", + "name": "Create workstream with valid values - Project 2", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"workStreamId2\", pm.response.json().id);", + "});" + ] + } + } + ], "request": { "method": "POST", "header": [ { - "key": "Content-Type", - "value": "application/json", - "type": "text" + "key": "Authorization", + "value": "Bearer {{jwt-token}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "type": "text" + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 2\r\n }\r\n }" + "raw": "{\n\t\t\"name\": \"test project\",\n\t\t\"type\": \"generic\",\n\t\t\"status\": \"active\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade", + "raw": "{{api-url}}/projects/2/workstreams", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates", - "{{productTemplateId}}", - "upgrade" + "2", + "workstreams" ] - } + }, + "description": "Valid request body. Project should be created successfully." }, "response": [] }, { - "name": "Upgrade a product template with wrong model version", + "name": "Update work item - allow access", "request": { - "method": "POST", + "method": "PATCH", "header": [ { - "key": "Content-Type", - "value": "application/json", - "type": "text" + "key": "Authorization", + "value": "Bearer {{jwt-token-manager-40051334}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "type": "text" + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 1234\r\n }\r\n }" + "raw": "{\n\t\t\"name\": \"test project - updated\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade", + "raw": "{{api-url}}/projects/2/workstreams/{{workStreamId2}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates", - "{{productTemplateId}}", - "upgrade" + "2", + "workstreams", + "{{workStreamId2}}" ] - } + }, + "description": "Update the project name. If user don't have permission to the project than it should return 403." }, "response": [] }, { - "name": "Upgrade a product template without define form", + "name": "Update workstream - deny access", "request": { - "method": "POST", + "method": "PATCH", "header": [ { - "key": "Content-Type", - "value": "application/json", - "type": "text" + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "type": "text" + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \r\n}" + "raw": "{\n\t\t\"name\": \"work item - updated\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade", + "raw": "{{api-url}}/projects/2/workstreams/{{workStreamId2}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productTemplates", - "{{productTemplateId}}", - "upgrade" + "2", + "workstreams", + "{{workStreamId2}}" ] - } + }, + "description": "Update the project name. Name should be updated successfully." }, "response": [] } - ] + ], + "description": "Requests for all things projects.", + "protocolProfileBehavior": {} }, { - "name": "Project Type", + "name": "EventHandling and Integration with Direct Project API", "item": [ { - "name": "Create project type", - "event": [ - { - "listen": "test", - "script": { - "id": "fbc45946-a3f2-433a-8ec5-0af82b69d2bd", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"projectTypeId\", pm.response.json().key);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "mock direct projects", "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" - } - ], - "body": { - "mode": "raw", - "raw": "{\r\n \"key\": \"new key\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"http://example.com/icon4.ico\",\r\n \t\"question\": \"question 4\",\r\n \t\"info\": \"info 4\",\r\n \t\"aliases\": [\"key-41\", \"key_42\"],\r\n \t\"metadata\": {}\r\n }" - }, - "url": { - "raw": "{{api-url}}/projects/metadata/projectTypes", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "metadata", - "projectTypes" - ] - } - }, - "response": [] - }, - { - "name": "List project types", - "request": { - "method": "GET", - "header": [ + }, { "key": "Content-Type", "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" } ], "url": { - "raw": "{{api-url}}/projects/metadata/projectTypes", + "raw": "https://api.topcoder-dev.com/v3/direct/projects", + "protocol": "https", "host": [ - "{{api-url}}" + "api", + "topcoder-dev", + "com" ], "path": [ - "projects", - "metadata", - "projectTypes" + "v3", + "direct", + "projects" ] } }, "response": [] }, { - "name": "Get project type", + "name": " Create direct project when a new project is successfully created", "request": { - "method": "GET", + "method": "POST", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "metadata", - "projectTypes", - "{{projectTypeId}}" - ] - } - }, - "response": [] - }, - { - "name": "Update project type", - "request": { - "method": "PATCH", - "header": [ + }, { "key": "Content-Type", "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"displayName\": \"Chatbot-updated\"\r\n }" + "raw": "{\n \"type\": \"generic\",\n \"description\": \"test project\",\n \"details\": {},\n \"billingAccountId\": 123,\n \"name\": \"test project1\"\n }" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}", + "raw": "{{api-url}}/projects", "host": [ "{{api-url}}" ], "path": [ - "projects", - "metadata", - "projectTypes", - "{{projectTypeId}}" + "projects" ] } }, "response": [] }, { - "name": "Delete project type", + "name": "Response error from direct project service", "request": { - "method": "DELETE", + "method": "POST", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n\n \"role\": \"copilot\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "projectTypes", - "{{projectTypeId}}" + "{{projectId}}", + "members" ] } }, "response": [] - } - ] - }, - { - "name": "Product Category", - "item": [ + }, { - "name": "Create product category", - "event": [ - { - "listen": "test", - "script": { - "id": "06156797-ceb2-4f8c-9448-5c453adb7b7a", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"productCategoryId\", pm.response.json().key);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": " Add co-pilot when a co-pilot is added to a project", "request": { "method": "POST", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"key\": \"generic\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"icon\",\r\n \"question\": \"question\",\r\n \"info\": \"info\",\r\n \"aliases\": [\"key-1\", \"key-2\"]\r\n }" + "raw": "{\n\t\"role\": \"copilot\"\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/productCategories", + "raw": "{{api-url}}/projects/{{projectId}}/members", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productCategories" + "{{projectId}}", + "members" ] } }, "response": [] }, { - "name": "List product categories", + "name": "remove copilot from direct project when editing project member role", "request": { - "method": "GET", + "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/metadata/productCategories", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "metadata", - "productCategories" - ] - } - }, - "response": [] - }, - { - "name": "Get product category", - "request": { - "method": "GET", - "header": [ + }, { "key": "Content-Type", "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" } ], + "body": { + "mode": "raw", + "raw": " {\n \"role\": \"customer\",\n \"isPrimary\": true\n } " + }, "url": { - "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}", + "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productCategories", - "{{productCategoryId}}" + "{{projectId}}", + "members", + "{{memberId}}" ] } }, "response": [] }, { - "name": "Update product category", + "name": " Sync billing account id with direct", "request": { "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"displayName\": \"Chatbot-updated\"\r\n }" + "raw": "{\n \"billingAccountId\": 9999, \n \"name\": \"new project name\"\n }" }, "url": { - "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}", + "raw": "{{api-url}}/projects/{{projectId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productCategories", - "{{productCategoryId}}" + "{{projectId}}" ] } }, "response": [] }, { - "name": "Delete product category", + "name": "Delete co-pilot when a co-pilot is removed from a project", "request": { "method": "DELETE", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" + "raw": "" }, "url": { - "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}", + "raw": "{{api-url}}/projects/{{projectId}}/members/{{memberId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "productCategories", - "{{productCategoryId}}" + "{{projectId}}", + "members", + "{{memberId}}" ] } }, "response": [] } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJwc2hhaDEiLCJleHAiOjI0NjI0OTQ2MTgsInVzZXJJZCI6IjQwMTM1OTc4IiwiaWF0IjoxNDYyNDk0MDE4LCJlbWFpbCI6InBzaGFoMUB0ZXN0LmNvbSIsImp0aSI6ImY0ZTFhNTE0LTg5ODAtNDY0MC04ZWM1LWUzNmUzMWE3ZTg0OSJ9.XuNN7tpMOXvBG1QwWRQROj7NfuUbqhkjwn39Vy4tR5I", - "type": "string" - } - ] - }, "event": [ { "listen": "prerequest", "script": { - "id": "f0092ef5-e624-4c25-87b2-b6a9e4c81ec8", + "id": "ef96ac6a-0fc0-4a64-a4fe-5390e17afe67", "type": "text/javascript", "exec": [ "" @@ -4253,27 +4484,43 @@ { "listen": "test", "script": { - "id": "9183c429-a5e0-4bf9-96a2-89f4d66e9b0d", + "id": "12f9d794-0872-4058-aafa-77b89e72025b", "type": "text/javascript", "exec": [ "" ] } } - ] + ], + "protocolProfileBehavior": {} }, { - "name": "Project upgrade", + "name": "Project Phase", "item": [ { - "name": "Migrate project", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, + "name": "Create Phase", + "event": [ + { + "listen": "test", + "script": { + "id": "7050133a-b934-4faf-8101-d2e80b5c0710", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"phaseId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, { "key": "Content-Type", "value": "application/json" @@ -4281,24 +4528,39 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}" + "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t}\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/upgrade", + "raw": "{{api-url}}/projects/{{projectId}}/phases", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "upgrade" + "phases" ] } }, "response": [] }, { - "name": "Migrate project (completed)", + "name": "Create Phase with order", + "event": [ + { + "listen": "test", + "script": { + "id": "2f771afe-7b4e-4260-b04d-324e880eb61b", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"phaseId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -4313,24 +4575,39 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}" + "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t},\n\t\"order\": 1\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/upgrade", + "raw": "{{api-url}}/projects/{{projectId}}/phases", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "upgrade" + "phases" ] } }, "response": [] }, { - "name": "Migrate project with phase name", + "name": "Create Phase with productTemplateId", + "event": [ + { + "listen": "test", + "script": { + "id": "8415ad98-b3f6-4330-88b6-e1830da2e4f9", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"phaseId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [ @@ -4345,26 +4622,26 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}" + "raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t},\n\t\"order\": 1,\n\t\"productTemplateId\": {{productTemplateId}}\n}" }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/upgrade", + "raw": "{{api-url}}/projects/{{projectId}}/phases", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "upgrade" + "phases" ] } }, "response": [] }, { - "name": "Migrate project with phase name (completed)", + "name": "List Phase DB", "request": { - "method": "POST", + "method": "GET", "header": [ { "key": "Authorization", @@ -4375,324 +4652,363 @@ "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}" - }, "url": { - "raw": "{{api-url}}/projects/{{projectId}}/upgrade", + "raw": "{{api-url}}/projects/{{projectId}}/phases/db", "host": [ "{{api-url}}" ], "path": [ "projects", "{{projectId}}", - "upgrade" + "phases", + "db" ] } }, "response": [] - } - ], - "description": "Request to migrate projects." - }, - { - "name": "Timeline", - "item": [ + }, { - "name": "Create timeline", - "event": [ - { - "listen": "test", - "script": { - "id": "c066e7d4-537f-406e-a768-ec4bf73a2634", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"timelineId\", pm.response.json().id);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "List Phase DB with fields", "request": { - "method": "POST", + "method": "GET", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token-connectAdmin-40051336}}" + "key": "Content-Type", + "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{\r\n\t\"name\":\"new name\",\r\n\t\"description\":\"new description\",\r\n\t\"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n\t\"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n\t\"reference\": \"project\",\r\n\t\"referenceId\": {{projectId}}\r\n}" - }, "url": { - "raw": "{{api-url}}/timelines", + "raw": "{{api-url}}/projects/{{projectId}}/phases/db?fields=status,name,budget", "host": [ "{{api-url}}" ], "path": [ - "timelines" + "projects", + "{{projectId}}", + "phases", + "db" + ], + "query": [ + { + "key": "fields", + "value": "status,name,budget" + } ] } }, "response": [] }, { - "name": "Create timeline with templateId", - "event": [ - { - "listen": "test", - "script": { - "id": "ee729ed9-0072-4821-9141-3615ff66f728", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"timelineId\", pm.response.json().id);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "List Phase DB with sort", "request": { - "method": "POST", + "method": "GET", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token-connectAdmin-40051336}}" + "key": "Content-Type", + "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1,\r\n \"templateId\": 1\r\n}" - }, "url": { - "raw": "{{api-url}}/timelines", + "raw": "{{api-url}}/projects/{{projectId}}/phases/db?sort=status desc", "host": [ "{{api-url}}" ], "path": [ - "timelines" + "projects", + "{{projectId}}", + "phases", + "db" + ], + "query": [ + { + "key": "sort", + "value": "status desc" + } ] } }, "response": [] }, { - "name": "Create timeline with invalid data", + "name": "List Phase DB with sort by order", "request": { - "method": "POST", + "method": "GET", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token-connectAdmin-40051336}}" + "key": "Content-Type", + "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-28T00:00:00.000Z\",\r\n \"reference\": \"invalid\",\r\n \"referenceId\": 0\r\n}" - }, "url": { - "raw": "{{api-url}}/timelines", + "raw": "{{api-url}}/projects/{{projectId}}/phases/db?sort=order desc", "host": [ "{{api-url}}" ], "path": [ - "timelines" + "projects", + "{{projectId}}", + "phases", + "db" + ], + "query": [ + { + "key": "sort", + "value": "order desc" + } ] } }, "response": [] }, { - "name": "List timelines (filter by reference and referenceId)", + "name": "List Phase", "request": { "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", - "value": "Bearer {{jwt-token-copilot-40051332}}", - "disabled": true + "value": "Bearer {{jwt-token}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "type": "text" + "key": "Content-Type", + "value": "application/json" } ], "url": { - "raw": "{{api-url}}/timelines?reference=project&referenceId={{projectId}}", + "raw": "{{api-url}}/projects/{{projectId}}/phases", "host": [ "{{api-url}}" ], "path": [ - "timelines" - ], - "query": [ - { - "key": "reference", - "value": "project" - }, - { - "key": "referenceId", - "value": "{{projectId}}" - } + "projects", + "{{projectId}}", + "phases" ] } }, "response": [] }, { - "name": "Get timeline", + "name": "List Phase with fields", "request": { "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}", + "raw": "{{api-url}}/projects/{{projectId}}/phases?fields=status,name,budget", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}" + "projects", + "{{projectId}}", + "phases" + ], + "query": [ + { + "key": "fields", + "value": "status,name,budget" + } ] } }, "response": [] }, { - "name": "Update timeline", + "name": "List Phase with sort", "request": { - "method": "PATCH", + "method": "GET", "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, { "key": "Content-Type", "value": "application/json" - }, + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/phases?sort=status desc", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "phases" + ], + "query": [ + { + "key": "sort", + "value": "status desc" + } + ] + } + }, + "response": [] + }, + { + "name": "List Phase with sort by order", + "request": { + "method": "GET", + "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-01T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": {{projectId}}\r\n}" - }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}", + "raw": "{{api-url}}/projects/{{projectId}}/phases?sort=order desc", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}" + "projects", + "{{projectId}}", + "phases" + ], + "query": [ + { + "key": "sort", + "value": "order desc" + } ] } }, "response": [] }, { - "name": "Update timeline (startDate)", + "name": "Get Phase", "request": { - "method": "PATCH", + "method": "GET", "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, { "key": "Content-Type", "value": "application/json" - }, + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update Phase", + "request": { + "method": "PATCH", + "header": [ { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n}" + "raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t}\n}" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}" + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}" ] } }, "response": [] }, { - "name": "Update timeline (endDate)", + "name": "Update Phase with order", "request": { "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n}" + "raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t},\n\t\"order\": 1\n}" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}" + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}" ] } }, "response": [] }, { - "name": "Delete timeline", + "name": "Delete Phase", "request": { "method": "DELETE", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], "body": { @@ -4700,34 +5016,37 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}" + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}" ] } }, "response": [] } - ] + ], + "protocolProfileBehavior": {} }, { - "name": "Milestone", + "name": "Phase Products", "item": [ { - "name": "Create milestone", + "name": "Create Phase Product", "event": [ { "listen": "test", "script": { - "id": "8fd1d5e9-8e6e-4cd7-9010-b855308be069", + "id": "77f089b3-cbe6-4fb4-b54f-2a52d138a050", "exec": [ "pm.test(\"Status code is 201\", function () {", " pm.response.to.have.status(201);", - " pm.environment.set(\"milestoneId\", pm.response.json().id);", + " pm.environment.set(\"phaseProductId\", pm.response.json().id);", "});" ], "type": "text/javascript" @@ -4738,165 +5057,154 @@ "method": "POST", "header": [ { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{jwt-token}}" }, { - "key": "Authorization", - "value": "Bearer {{jwt-token-admin-40051333}}" + "key": "Content-Type", + "value": "application/json" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestone 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 4,\r\n \"startDate\": \"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-31T00:00:00.000Z\",\r\n \"status\": \"open\",\r\n \"type\": \"type3\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 2,\r\n 3,\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 3\",\r\n \"activeText\": \"activeText 3\",\r\n \"completedText\": \"completedText 3\",\r\n \"blockedText\": \"blockedText 3\"\r\n}" + "raw": "{\n\t\"name\": \"test phase product\",\n\t\"type\": \"type 1\",\n\t\"estimatedPrice\": 10\n}" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones" + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}", + "products" ] } }, "response": [] }, { - "name": "Create milestone with invalid data", + "name": "List Phase DB Products", "request": { - "method": "POST", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", - "value": "Bearer {{jwt-token-member-40051331}}" + "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-04T00:00:00.000Z\"\r\n}" - }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/db", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones" + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}", + "products", + "db" ] } }, "response": [] }, { - "name": "List milestones", + "name": "List Phase Products", "request": { "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", - "value": "Bearer {{jwt-token}}", - "type": "text" + "value": "Bearer {{jwt-token}}" } ], "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones" + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}", + "products" ] } }, "response": [] }, { - "name": "List milestones (sort)", + "name": "Get Phase Product", "request": { "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", - "value": "Bearer {{jwt-token-copilot-40051332}}" + "value": "Bearer {{jwt-token}}" } ], "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones?sort=order desc", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones" - ], - "query": [ - { - "key": "sort", - "value": "order desc" - } + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}", + "products", + "{{phaseProductId}}" ] } }, "response": [] }, { - "name": "Get milestone", + "name": "Update Phase Product", "request": { - "method": "GET", + "method": "PATCH", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" } ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"test phase product xxx\",\n\t\"type\": \"type 2\",\n\t\"templateId\": 10,\n\t\"estimatedPrice\": 1.234567,\n\t\"actualPrice\": 2.34567,\n\t\"details\": {\n\t\t\"message\": \"this is a JSON type. You can use any json\"\n\t}\n}" + }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones", - "{{milestoneId}}" - ] - } + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}", + "products", + "{{phaseProductId}}" + ] + } }, "response": [] }, { - "name": "Update milestone", + "name": "Delete Phase Product", "request": { - "method": "PATCH", + "method": "DELETE", "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, { "key": "Authorization", "value": "Bearer {{jwt-token}}" @@ -4904,27 +5212,50 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-09-28T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + "raw": "" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/{{phaseProductId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones", - "{{milestoneId}}" + "projects", + "{{projectId}}", + "phases", + "{{phaseId}}", + "products", + "{{phaseProductId}}" ] } }, "response": [] - }, + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Project Templates", + "item": [ { - "name": "Update milestone (active)", + "name": "Create project template", + "event": [ + { + "listen": "test", + "script": { + "id": "2f79c07b-8076-4715-abf7-1d6903df444f", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -4937,27 +5268,41 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"active\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"scope\":{\r\n \"scope1\":\"scope 1\"\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\"\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones", - "{{milestoneId}}" + "projects", + "metadata", + "projectTemplates" ] } }, "response": [] }, { - "name": "Update milestone (completed)", + "name": "Create project template with form, priceConfig, planConfig", + "event": [ + { + "listen": "test", + "script": { + "id": "4c442ea3-0834-4a30-8044-a4e94fd4ea2d", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -4970,27 +5315,41 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"completed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones", - "{{milestoneId}}" + "projects", + "metadata", + "projectTemplates" ] } }, "response": [] }, { - "name": "Update milestone (order 1 => 2)", + "name": "Create project template with only form key", + "event": [ + { + "listen": "test", + "script": { + "id": "7d0ae3ca-fe2d-40eb-b5c8-9b03955babec", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -5003,27 +5362,26 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\"\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones", - "{{milestoneId}}" + "projects", + "metadata", + "projectTemplates" ] } }, "response": [] }, { - "name": "Update milestone (order 2 => 1)", + "name": "Create project template with wrong form key", "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -5036,27 +5394,26 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"wrong-key\"\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones", - "{{milestoneId}}" + "projects", + "metadata", + "projectTemplates" ] } }, "response": [] }, { - "name": "Update milestone (order 1 => 3)", + "name": "Create project template with wrong model version", "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -5069,27 +5426,26 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"question\": \"question 1\",\r\n \"info\": \"info 1\",\r\n \"aliases\": [\"key-1\", \"key_1\"],\r\n \"form\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\r\n \t\"version\": 1123\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones", - "{{milestoneId}}" + "projects", + "metadata", + "projectTemplates" ] } }, "response": [] }, { - "name": "Update milestone (order 3 => 1)", + "name": "List project templates", "request": { - "method": "PATCH", + "method": "GET", "header": [ { "key": "Content-Type", @@ -5100,29 +5456,24 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" - }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "raw": "{{api-url}}/projects/metadata/projectTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones", - "{{milestoneId}}" + "projects", + "metadata", + "projectTemplates" ] } }, "response": [] }, { - "name": "Delete milestone", + "name": "Get project template", "request": { - "method": "DELETE", + "method": "GET", "header": [ { "key": "Content-Type", @@ -5133,47 +5484,23 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "" - }, "url": { - "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", - "{{timelineId}}", - "milestones", - "{{milestoneId}}" + "projects", + "metadata", + "projectTemplates", + "{{projectTemplateId}}" ] } }, "response": [] - } - ] - }, - { - "name": "Milestone Template", - "item": [ + }, { - "name": "Create milestone template", - "event": [ - { - "listen": "test", - "script": { - "id": "3dbf8b29-2498-4b05-93de-14d809ccc285", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"milestoneTemplateId\", pm.response.json().id);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Upgrade a project template with from, priceConfig, planConfig", "request": { "method": "POST", "header": [ @@ -5183,29 +5510,31 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-admin-40051333}}" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestoneTemplate 1\",\r\n \"description\": \"description 1\",\r\n \"duration\": 11,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": {{productTemplateId}},\r\n\t\"metadata\": {}\r\n}" + "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 2\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"qa\",\t\r\n \t \"version\": 2\t\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates" + "projectTemplates", + "{{projectTemplateId}}", + "upgrade" ] } }, "response": [] }, { - "name": "Create milestone template with invalid referenceId", + "name": "Upgrade a project template with wrong model version", "request": { "method": "POST", "header": [ @@ -5215,29 +5544,31 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-admin-40051333}}" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1000,\r\n\t\"metadata\": {}\r\n}" + "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"priceConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n },\r\n \"planConfig\": {\r\n \t\"key\": \"dev\",\t\r\n \t \"version\": 1234\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates" + "projectTemplates", + "{{projectTemplateId}}", + "upgrade" ] } }, "response": [] }, { - "name": "Create milestone template with invalid data", + "name": "Upgrade a project template without define form, priceConfig, planConfig", "request": { "method": "POST", "header": [ @@ -5247,7 +5578,7 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-admin-40051333}}" + "value": "Bearer {{jwt-token}}" } ], "body": { @@ -5255,23 +5586,25 @@ "raw": "{\r\n}" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates" + "projectTemplates", + "{{projectTemplateId}}", + "upgrade" ] } }, "response": [] }, { - "name": "Clone milestone template", + "name": "Update project template", "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -5279,32 +5612,32 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-admin-40051333}}" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "clone" + "projectTemplates", + "{{projectTemplateId}}" ] } }, "response": [] }, { - "name": "Clone milestone template with invalid referenceId", + "name": "Update project template with define form, priceConfig, planConfig", "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -5312,32 +5645,32 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-admin-40051333}}" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2000\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n \"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "clone" + "projectTemplates", + "{{projectTemplateId}}" ] } }, "response": [] }, { - "name": "Clone milestone template with invalid sourceReferenceId", + "name": "Update project template with wrong form, priceConfig, planConfig", "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -5345,32 +5678,32 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-admin-40051333}}" + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1000,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"app\",\r\n\t\"form\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"priceConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n },\r\n \"planConfig\": {\r\n \"key\": \"dev\",\r\n \"version\": 1123\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "clone" + "projectTemplates", + "{{projectTemplateId}}" ] } }, "response": [] }, { - "name": "List milestone templates", + "name": "Delete project template", "request": { - "method": "GET", + "method": "DELETE", "header": [ { "key": "Content-Type", @@ -5378,27 +5711,53 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-copilot-40051332}}" + "value": "Bearer {{jwt-token}}" } ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" + }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", + "raw": "{{api-url}}/projects/metadata/projectTemplates/{{projectTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates" + "projectTemplates", + "{{projectTemplateId}}" ] } }, "response": [] - }, + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Product Templates", + "item": [ { - "name": "List milestone templates (filter)", + "name": "Create product template", + "event": [ + { + "listen": "test", + "script": { + "id": "b5aaf185-6026-4b58-b9b8-56616109cd5a", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"productTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -5406,37 +5765,46 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-copilot-40051332}}" + "value": "Bearer {{jwt-token}}" } ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"{{productCategoryId}}\",\r\n \"subCategory\": \"app\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"template\": {\r\n \"template1\": {\r\n \"name\": \"template 1\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1\"\r\n },\r\n \"others\": [\"others 11\", \"others 12\"]\r\n },\r\n \"template2\": {\r\n \"name\": \"template 2\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 2\"\r\n },\r\n \"others\": [\"others 21\", \"others 22\"]\r\n }\r\n }\r\n }" + }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId={{productTemplateId}}", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates" - ], - "query": [ - { - "key": "reference", - "value": "productTemplate" - }, - { - "key": "referenceId", - "value": "{{productTemplateId}}" - } + "productTemplates" ] } }, "response": [] }, { - "name": "List milestone templates (sort)", + "name": "Create product template with form", + "event": [ + { + "listen": "test", + "script": { + "id": "d5a2af2e-97d2-415c-a533-1d52dd4003c7", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"productTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -5444,41 +5812,31 @@ }, { "key": "Authorization", - "value": "Bearer {{jwt-token-copilot-40051332}}" + "value": "Bearer {{jwt-token}}" } ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1\r\n\t}\r\n }" + }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId={{productTemplateId}}&sort=order desc", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates" - ], - "query": [ - { - "key": "reference", - "value": "productTemplate" - }, - { - "key": "referenceId", - "value": "{{productTemplateId}}" - }, - { - "key": "sort", - "value": "order desc" - } + "productTemplates" ] } }, "response": [] }, { - "name": "Get milestone template", + "name": "Create product template with wrong form key", "request": { - "method": "GET", + "method": "POST", "header": [ { "key": "Content-Type", @@ -5489,25 +5847,28 @@ "value": "Bearer {{jwt-token}}" } ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"wrong-key\"\r\n\t}\r\n }" + }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "{{milestoneTemplateId}}" + "productTemplates" ] } }, "response": [] }, { - "name": "Update milestone", + "name": "Create product template with wrong model version", "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -5520,27 +5881,26 @@ ], "body": { "mode": "raw", - "raw": "{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" + "raw": "{\r\n \"name\": \"name 1\",\r\n \"productKey\": \"productKey 1\",\r\n \"category\": \"key1\",\r\n \"icon\": \"http://example.com/icon1.ico\",\r\n \"brief\": \"brief 1\",\r\n \"details\": \"details 1\",\r\n \"aliases\": [\"product key 1\", \"product_key_1\"],\r\n \"form\": {\r\n\t\t\"key\": \"dev\",\r\n\t\t\"version\": 1123\r\n\t}\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "{{milestoneTemplateId}}" + "productTemplates" ] } }, "response": [] }, { - "name": "Update milestone (order 1 => 2)", + "name": "List product templates", "request": { - "method": "PATCH", + "method": "GET", "header": [ { "key": "Content-Type", @@ -5551,29 +5911,24 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 2,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": {{productTemplateId}}\r\n}" - }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "raw": "{{api-url}}/projects/metadata/productTemplates", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "{{milestoneTemplateId}}" + "productTemplates" ] } }, "response": [] }, { - "name": "Update milestone (order 2 => 1)", + "name": "Get product template", "request": { - "method": "PATCH", + "method": "GET", "header": [ { "key": "Content-Type", @@ -5584,27 +5939,23 @@ "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" - }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "{{milestoneTemplateId}}" + "productTemplates", + "{{productTemplateId}}" ] } }, "response": [] }, { - "name": "Update milestone (order 1 => 3)", + "name": "Update product template", "request": { "method": "PATCH", "header": [ @@ -5619,27 +5970,27 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 3,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" + "raw": "{\r\n \"name\":\"new name\",\r\n \"productKey\":\"new productKey\",\r\n \"category\":\"key1\",\r\n \"icon\":\"http://example.com/icon-new.ico\",\r\n \"brief\": \"new brief\",\r\n \"details\": \"new details\",\r\n \"aliases\":{\r\n \"alias1\":\"scope 1\",\r\n \"alias2\": [\"a\"]\r\n },\r\n \"template\":{\r\n \"template1\":\"template 1\",\r\n \"template2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "{{milestoneTemplateId}}" + "productTemplates", + "{{productTemplateId}}" ] } }, "response": [] }, { - "name": "Update milestone (order 3 => 1)", + "name": "Delete product template", "request": { - "method": "PATCH", + "method": "DELETE", "header": [ { "key": "Content-Type", @@ -5652,734 +6003,667 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" + "raw": "" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "{{milestoneTemplateId}}" + "productTemplates", + "{{productTemplateId}}" ] } }, "response": [] }, { - "name": "Update milestone with metadata", + "name": "Upgrade a product template with form", "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", - "value": "application/json" + "value": "application/json", + "type": "text" }, { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer {{jwt-token}}", + "type": "text" } ], "body": { "mode": "raw", - "raw": "{\r\n\t\"name\": \"milestoneTemplate 5-updated\",\r\n\t\"description\": \"description 5-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type5-updated\",\r\n\t\"order\": 5,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {\r\n \"metadata1\": {\r\n \"name\": \"metadata 1 - update\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1 - update\",\r\n \"newDetails\": \"new\"\r\n },\r\n \"others\": [\"others new\"]\r\n },\r\n \"metadata3\": {\r\n \"name\": \"metadata 3\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 3\"\r\n },\r\n \"others\": [\"others 31\", \"others 32\"]\r\n }\r\n }\r\n}" + "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 2\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "{{milestoneTemplateId}}" + "productTemplates", + "{{productTemplateId}}", + "upgrade" ] } }, "response": [] }, { - "name": "Delete milestone", + "name": "Upgrade a product template with wrong model version", "request": { - "method": "DELETE", + "method": "POST", "header": [ { "key": "Content-Type", - "value": "application/json" + "value": "application/json", + "type": "text" }, { "key": "Authorization", - "value": "Bearer {{jwt-token}}" + "value": "Bearer {{jwt-token}}", + "type": "text" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\r\n \"form\": {\r\n \t\"key\": \"dev\",\t\r\n \t\"version\": 1234\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ - "timelines", + "projects", "metadata", - "milestoneTemplates", - "{{milestoneTemplateId}}" + "productTemplates", + "{{productTemplateId}}", + "upgrade" ] } }, "response": [] - } - ] - }, - { - "name": "Metadata", - "item": [ + }, { - "name": "Get all metadata", + "name": "Upgrade a product template without define form", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "GET", + "method": "POST", "header": [ { - "key": "", - "value": "", + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}", "type": "text" } ], - "url": { - "raw": "{{api-url}}/projects/metadata", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "metadata" - ] - } - }, - "response": [] - }, - { - "name": "Get all metadata with includeAllVersion", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "body": { + "mode": "raw", + "raw": "{\r\n \r\n}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata?includeAllReferred=true", + "raw": "{{api-url}}/projects/metadata/productTemplates/{{productTemplateId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata" - ], - "query": [ - { - "key": "includeAllReferred", - "value": "true" - } + "metadata", + "productTemplates", + "{{productTemplateId}}", + "upgrade" ] } }, "response": [] } - ] + ], + "protocolProfileBehavior": {} }, { - "name": "Form Version", + "name": "Project Type", "item": [ { - "name": "List forms", + "name": "Create project type", + "event": [ + { + "listen": "test", + "script": { + "id": "fbc45946-a3f2-433a-8ec5-0af82b69d2bd", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"projectTypeId\", pm.response.json().key);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"key\": \"new key\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"http://example.com/icon4.ico\",\r\n \t\"question\": \"question 4\",\r\n \t\"info\": \"info 4\",\r\n \t\"aliases\": [\"key-41\", \"key_42\"],\r\n \t\"metadata\": {}\r\n }" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions", + "raw": "{{api-url}}/projects/metadata/projectTypes", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "{{formKey}}", - "versions" + "projectTypes" ] } }, "response": [] }, { - "name": "Get a particular version", + "name": "List project types", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, "method": "GET", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], "url": { - "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}", + "raw": "{{api-url}}/projects/metadata/projectTypes", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "{{formKey}}", - "versions", - "{{formVersion}}" + "projectTypes" ] } }, "response": [] }, { - "name": "Get latest version", + "name": "Get project type", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, "method": "GET", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], "url": { - "raw": "{{api-url}}/projects/metadata/form/{{formKey}}", + "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "{{formKey}}" + "projectTypes", + "{{projectTypeId}}" ] } }, "response": [] }, { - "name": "Create form", - "event": [ - { - "listen": "test", - "script": { - "id": "94f6be66-34cc-40c8-80c2-b27dd93ed527", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"formKey\", pm.response.json().key);", - " pm.environment.set(\"formVersion\", pm.response.json().version);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Update project type", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + "raw": "{\r\n \"displayName\": \"Chatbot-updated\"\r\n }" }, "url": { - "raw": "{{api-url}}/projects/metadata/form/dev/versions", + "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "dev", - "versions" + "projectTypes", + "{{projectTypeId}}" ] } }, "response": [] }, { - "name": "Update form", + "name": "Delete project type", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "PATCH", + "method": "DELETE", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }" + "raw": "" }, "url": { - "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}", + "raw": "{{api-url}}/projects/metadata/projectTypes/{{projectTypeId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "{{formKey}}", - "versions", - "{{formVersion}}" + "projectTypes", + "{{projectTypeId}}" ] } }, "response": [] - }, + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Product Category", + "item": [ { - "name": "Delete form", + "name": "Create product category", + "event": [ + { + "listen": "test", + "script": { + "id": "06156797-ceb2-4f8c-9448-5c453adb7b7a", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"productCategoryId\", pm.response.json().key);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "DELETE", + "method": "POST", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "" - }, - "url": { - "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "metadata", - "form", - "{{formKey}}", - "versions", - "{{formVersion}}" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Form Revision", - "item": [ - { - "name": "List all revision for version", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "raw": "{\r\n \"key\": \"generic\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"icon\",\r\n \"question\": \"question\",\r\n \"info\": \"info\",\r\n \"aliases\": [\"key-1\", \"key-2\"]\r\n }" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions", + "raw": "{{api-url}}/projects/metadata/productCategories", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "{{formKey}}", - "versions", - "{{formVersion}}", - "revisions" + "productCategories" ] } }, "response": [] }, { - "name": "Get a particular revision", + "name": "List product categories", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, "method": "GET", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], "url": { - "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions/{{formRevision}}", + "raw": "{{api-url}}/projects/metadata/productCategories", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "{{formKey}}", - "versions", - "{{formVersion}}", - "revisions", - "{{formRevision}}" + "productCategories" ] } }, "response": [] }, { - "name": "Create form", - "event": [ - { - "listen": "test", - "script": { - "id": "dbe5ec9f-022c-4ec5-b58c-d19c15430b61", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"formRevision\", pm.response.json().revision);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Get product category", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "POST", + "method": "GET", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" - }, "url": { - "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions", + "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "{{formKey}}", - "versions", - "{{formVersion}}", - "revisions" + "productCategories", + "{{productCategoryId}}" ] } }, "response": [] }, { - "name": "Create for no exist key", + "name": "Update product category", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + "raw": "{\r\n \"displayName\": \"Chatbot-updated\"\r\n }" }, "url": { - "raw": "{{api-url}}/projects/metadata/form/no-exist-2222key36/versions/1/revisions", + "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "no-exist-2222key36", - "versions", - "1", - "revisions" + "productCategories", + "{{productCategoryId}}" ] } }, "response": [] }, { - "name": "Delete revision", + "name": "Delete product category", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, "method": "DELETE", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\r\n \"name\":\"new name\",\r\n \"key\":\"new key\",\r\n \"category\":\"new category\",\r\n \"scope\":{\r\n \"scope1\":\"scope 1\",\r\n \"scope2\": [\"a\"]\r\n },\r\n \"phases\":{\r\n \"phase1\":\"phase 1\",\r\n \"phase2\": {\r\n \t\"another\": \"another\"\r\n }\r\n }\r\n }" }, "url": { - "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions/{{formRevision}}", + "raw": "{{api-url}}/projects/metadata/productCategories/{{productCategoryId}}", "host": [ "{{api-url}}" ], "path": [ "projects", "metadata", - "form", - "{{formKey}}", - "versions", - "{{formVersion}}", - "revisions", - "{{formRevision}}" + "productCategories", + "{{productCategoryId}}" ] } }, "response": [] } - ] + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJhZG1pbmlzdHJhdG9yIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJwc2hhaDEiLCJleHAiOjI0NjI0OTQ2MTgsInVzZXJJZCI6IjQwMTM1OTc4IiwiaWF0IjoxNDYyNDk0MDE4LCJlbWFpbCI6InBzaGFoMUB0ZXN0LmNvbSIsImp0aSI6ImY0ZTFhNTE0LTg5ODAtNDY0MC04ZWM1LWUzNmUzMWE3ZTg0OSJ9.XuNN7tpMOXvBG1QwWRQROj7NfuUbqhkjwn39Vy4tR5I", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "id": "f0092ef5-e624-4c25-87b2-b6a9e4c81ec8", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "9183c429-a5e0-4bf9-96a2-89f4d66e9b0d", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "protocolProfileBehavior": {} }, { - "name": "Price Config Version", + "name": "Project upgrade", "item": [ { - "name": "List price configs", + "name": "Migrate project", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions", + "raw": "{{api-url}}/projects/{{projectId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "priceConfig", - "dev", - "versions" + "{{projectId}}", + "upgrade" ] } }, "response": [] }, { - "name": "Get a particular version", + "name": "Migrate project (completed)", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3\n\t}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}", + "raw": "{{api-url}}/projects/{{projectId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "priceConfig", - "{{priceKey}}", - "versions", - "{{priceVersion}}" + "{{projectId}}", + "upgrade" ] } }, "response": [] }, { - "name": "Get latest version", + "name": "Migrate project with phase name", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}", + "raw": "{{api-url}}/projects/{{projectId}}/upgrade", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "priceConfig", - "{{priceKey}}" + "{{projectId}}", + "upgrade" ] } }, "response": [] }, { - "name": "Create priceConfig", + "name": "Migrate project with phase name (completed)", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\t\"targetVersion\": \"v3\",\n\t\t\"defaultProductTemplateId\": 3,\n\t\t\"phaseName\": \"Custom phase name\"\n\t}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/upgrade", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "upgrade" + ] + } + }, + "response": [] + } + ], + "description": "Request to migrate projects.", + "protocolProfileBehavior": {} + }, + { + "name": "Timeline", + "item": [ + { + "name": "Create timeline", "event": [ { "listen": "test", "script": { - "id": "e440c87c-49ff-4443-b9bf-b44d4e9a480f", + "id": "c066e7d4-537f-406e-a768-ec4bf73a2634", "exec": [ "pm.test(\"Status code is 201\", function () {", " pm.response.to.have.status(201);", - " pm.environment.set(\"priceKey\", pm.response.json().key);", - " pm.environment.set(\"priceVersion\", pm.response.json().version);", + " pm.environment.set(\"timelineId\", pm.response.json().id);", "});" ], "type": "text/javascript" @@ -6387,810 +6671,3749 @@ } ], "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, "method": "POST", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-connectAdmin-40051336}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + "raw": "{\r\n\t\"name\":\"new name\",\r\n\t\"description\":\"new description\",\r\n\t\"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n\t\"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n\t\"reference\": \"project\",\r\n\t\"referenceId\": {{projectId}}\r\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions", + "raw": "{{api-url}}/timelines", "host": [ "{{api-url}}" ], "path": [ - "projects", - "metadata", - "priceConfig", - "dev", - "versions" + "timelines" ] } }, "response": [] }, { - "name": "Update priceConfig", + "name": "Create timeline with templateId", + "event": [ + { + "listen": "test", + "script": { + "id": "ee729ed9-0072-4821-9141-3615ff66f728", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"timelineId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-connectAdmin-40051336}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }" + "raw": "{\r\n \"name\":\"new name\",\r\n \"description\":\"new description\",\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1,\r\n \"templateId\": 1\r\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}", + "raw": "{{api-url}}/timelines", "host": [ "{{api-url}}" ], "path": [ - "projects", - "metadata", - "priceConfig", - "{{priceKey}}", - "versions", - "{{priceVersion}}" + "timelines" ] } }, "response": [] }, { - "name": "Delete priceConfig", + "name": "Create timeline with invalid data", "request": { - "auth": { - "type": "bearer", - "bearer": [ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-connectAdmin-40051336}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"startDate\":\"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-28T00:00:00.000Z\",\r\n \"reference\": \"invalid\",\r\n \"referenceId\": 0\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines" + ] + } + }, + "response": [] + }, + { + "name": "List timelines (filter by reference and referenceId)", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}", + "disabled": true + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}", + "type": "text" + } + ], + "url": { + "raw": "{{api-url}}/timelines?reference=project&referenceId={{projectId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines" + ], + "query": [ { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" + "key": "reference", + "value": "project" + }, + { + "key": "referenceId", + "value": "{{projectId}}" } ] - }, - "method": "DELETE", + } + }, + "response": [] + }, + { + "name": "Get timeline", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update timeline", + "request": { + "method": "PATCH", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-01T00:00:00.000Z\",\r\n \"endDate\": null,\r\n \"reference\": \"project\",\r\n \"referenceId\": {{projectId}}\r\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}", + "raw": "{{api-url}}/timelines/{{timelineId}}", "host": [ "{{api-url}}" ], "path": [ - "projects", - "metadata", - "priceConfig", - "{{priceKey}}", - "versions", - "{{priceVersion}}" + "timelines", + "{{timelineId}}" ] } }, "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "id": "59182724-4332-4d76-90ea-f7520a7b1be9", - "type": "text/javascript", - "exec": [ - "" - ] - } }, { - "listen": "test", - "script": { - "id": "abc13dca-e8a4-4995-970f-00e5889a5f2d", - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] - }, - { - "name": "Price Config Revision", - "item": [ - { - "name": "List all revision for version", + "name": "Update timeline (startDate)", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}" ] + } + }, + "response": [] + }, + { + "name": "Update timeline (endDate)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"timeline 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"reference\": \"project\",\r\n \"referenceId\": 1\r\n}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions/3/revisions", + "raw": "{{api-url}}/timelines/{{timelineId}}", "host": [ "{{api-url}}" ], "path": [ - "projects", - "metadata", - "priceConfig", - "dev", - "versions", - "3", - "revisions" + "timelines", + "{{timelineId}}" ] } }, "response": [] }, { - "name": "Get a particular revision", + "name": "Delete timeline", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}" ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Milestone", + "item": [ + { + "name": "Create milestone", + "event": [ + { + "listen": "test", + "script": { + "id": "8fd1d5e9-8e6e-4cd7-9010-b855308be069", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"milestoneId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 4,\r\n \"startDate\": \"2018-05-29T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-30T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-31T00:00:00.000Z\",\r\n \"status\": \"open\",\r\n \"type\": \"type3\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 2,\r\n 3,\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 3\",\r\n \"activeText\": \"activeText 3\",\r\n \"completedText\": \"completedText 3\",\r\n \"blockedText\": \"blockedText 3\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones" + ] + } + }, + "response": [] + }, + { + "name": "Create milestone with invalid data", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-member-40051331}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"startDate\": \"2018-05-05T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-04T00:00:00.000Z\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones" + ] + } + }, + "response": [] + }, + { + "name": "List milestones", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}", + "type": "text" + } + ], + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones" + ] + } + }, + "response": [] + }, + { + "name": "List milestones (sort)", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}" + } + ], + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones?sort=order desc", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones" + ], + "query": [ + { + "key": "sort", + "value": "order desc" + } + ] + } + }, + "response": [] + }, + { + "name": "Get milestone", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-09-28T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone - paused", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"status\": \"paused\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"statusComment\": \"milestone paused\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone - resume", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"status\": \"resume\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"statusComment\": \"milestone resume\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (active)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"active\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (completed)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 2-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-10-28T00:00:00.000Z\",\r\n \"status\": \"completed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (order 1 => 2)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (order 2 => 1)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (order 1 => 3)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (order 3 => 1)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete milestone", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}/milestones/{{milestoneId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}", + "milestones", + "{{milestoneId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Milestone Template", + "item": [ + { + "name": "Create milestone template", + "event": [ + { + "listen": "test", + "script": { + "id": "3dbf8b29-2498-4b05-93de-14d809ccc285", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"milestoneTemplateId\", pm.response.json().id);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestoneTemplate 1\",\r\n \"description\": \"description 1\",\r\n \"duration\": 11,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": {{productTemplateId}},\r\n\t\"metadata\": {}\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates" + ] + } + }, + "response": [] + }, + { + "name": "Create milestone template with invalid referenceId", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestoneTemplate 3\",\r\n \"description\": \"description 3\",\r\n \"duration\": 33,\r\n \"type\": \"type3\",\r\n \"order\": 1,\r\n \"activeText\": \"activeText 1\",\r\n \"completedText\": \"completedText 1\",\r\n \"blockedText\": \"blockedText 1\",\r\n \"plannedText\": \"planned Text 1\",\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1000,\r\n\t\"metadata\": {}\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates" + ] + } + }, + "response": [] + }, + { + "name": "Create milestone template with invalid data", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates" + ] + } + }, + "response": [] + }, + { + "name": "Clone milestone template", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "clone" + ] + } + }, + "response": [] + }, + { + "name": "Clone milestone template with invalid referenceId", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2000\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "clone" + ] + } + }, + "response": [] + }, + { + "name": "Clone milestone template with invalid sourceReferenceId", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"sourceReference\": \"productTemplate\",\r\n \"sourceReferenceId\": 1000,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": 2\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/clone", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "clone" + ] + } + }, + "response": [] + }, + { + "name": "List milestone templates", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}" + } + ], + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates" + ] + } + }, + "response": [] + }, + { + "name": "List milestone templates (filter)", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}" + } + ], + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId={{productTemplateId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates" + ], + "query": [ + { + "key": "reference", + "value": "productTemplate" + }, + { + "key": "referenceId", + "value": "{{productTemplateId}}" + } + ] + } + }, + "response": [] + }, + { + "name": "List milestone templates (sort)", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}" + } + ], + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates?reference=productTemplate&referenceId={{productTemplateId}}&sort=order desc", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates" + ], + "query": [ + { + "key": "reference", + "value": "productTemplate" + }, + { + "key": "referenceId", + "value": "{{productTemplateId}}" + }, + { + "key": "sort", + "value": "order desc" + } + ] + } + }, + "response": [] + }, + { + "name": "Get milestone template", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "{{milestoneTemplateId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "{{milestoneTemplateId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (order 1 => 2)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 2,\r\n \"reference\": \"productTemplate\",\r\n \"referenceId\": {{productTemplateId}}\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "{{milestoneTemplateId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (order 2 => 1)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"name\": \"milestoneTemplate 1-updated\",\r\n\t\"description\": \"description 1-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type1-updated\",\r\n\t\"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "{{milestoneTemplateId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (order 1 => 3)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 3,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "{{milestoneTemplateId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone (order 3 => 1)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"milestoneTemplate 1-updated\",\r\n \"description\": \"description 1-updated\",\r\n \"duration\": 34,\r\n \"type\": \"type1-updated\",\r\n \"order\": 1,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "{{milestoneTemplateId}}" + ] + } + }, + "response": [] + }, + { + "name": "Update milestone with metadata", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"name\": \"milestoneTemplate 5-updated\",\r\n\t\"description\": \"description 5-updated\",\r\n\t\"duration\": 34,\r\n\t\"type\": \"type5-updated\",\r\n\t\"order\": 5,\r\n\t\"reference\": \"productTemplate\",\r\n\t\"referenceId\": 1,\r\n\t\"metadata\": {\r\n \"metadata1\": {\r\n \"name\": \"metadata 1 - update\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 1 - update\",\r\n \"newDetails\": \"new\"\r\n },\r\n \"others\": [\"others new\"]\r\n },\r\n \"metadata3\": {\r\n \"name\": \"metadata 3\",\r\n \"details\": {\r\n \"anyDetails\": \"any details 3\"\r\n },\r\n \"others\": [\"others 31\", \"others 32\"]\r\n }\r\n }\r\n}" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "{{milestoneTemplateId}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete milestone", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/timelines/metadata/milestoneTemplates/{{milestoneTemplateId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "metadata", + "milestoneTemplates", + "{{milestoneTemplateId}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Metadata", + "item": [ + { + "name": "Get all metadata", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "", + "value": "", + "type": "text" + } + ], + "url": { + "raw": "{{api-url}}/projects/metadata", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata" + ] + } + }, + "response": [] + }, + { + "name": "Get all metadata with includeAllVersion", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata?includeAllReferred=true", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata" + ], + "query": [ + { + "key": "includeAllReferred", + "value": "true" + } + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Form Version", + "item": [ + { + "name": "List forms", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "{{formKey}}", + "versions" + ] + } + }, + "response": [] + }, + { + "name": "Get a particular version", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "{{formKey}}", + "versions", + "{{formVersion}}" + ] + } + }, + "response": [] + }, + { + "name": "Get latest version", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "{{formKey}}" + ] + } + }, + "response": [] + }, + { + "name": "Create form", + "event": [ + { + "listen": "test", + "script": { + "id": "94f6be66-34cc-40c8-80c2-b27dd93ed527", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"formKey\", pm.response.json().key);", + " pm.environment.set(\"formVersion\", pm.response.json().version);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/form/dev/versions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "dev", + "versions" + ] + } + }, + "response": [] + }, + { + "name": "Update form", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "{{formKey}}", + "versions", + "{{formVersion}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete form", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "{{formKey}}", + "versions", + "{{formVersion}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Form Revision", + "item": [ + { + "name": "List all revision for version", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "{{formKey}}", + "versions", + "{{formVersion}}", + "revisions" + ] + } + }, + "response": [] + }, + { + "name": "Get a particular revision", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions/{{formRevision}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "{{formKey}}", + "versions", + "{{formVersion}}", + "revisions", + "{{formRevision}}" + ] + } + }, + "response": [] + }, + { + "name": "Create form", + "event": [ + { + "listen": "test", + "script": { + "id": "dbe5ec9f-022c-4ec5-b58c-d19c15430b61", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"formRevision\", pm.response.json().revision);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "{{formKey}}", + "versions", + "{{formVersion}}", + "revisions" + ] + } + }, + "response": [] + }, + { + "name": "Create for no exist key", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/form/no-exist-2222key36/versions/1/revisions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "no-exist-2222key36", + "versions", + "1", + "revisions" + ] + } + }, + "response": [] + }, + { + "name": "Delete revision", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/form/{{formKey}}/versions/{{formVersion}}/revisions/{{formRevision}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "form", + "{{formKey}}", + "versions", + "{{formVersion}}", + "revisions", + "{{formRevision}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Price Config Version", + "item": [ + { + "name": "List price configs", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "dev", + "versions" + ] + } + }, + "response": [] + }, + { + "name": "Get a particular version", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "{{priceKey}}", + "versions", + "{{priceVersion}}" + ] + } + }, + "response": [] + }, + { + "name": "Get latest version", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "{{priceKey}}" + ] + } + }, + "response": [] + }, + { + "name": "Create priceConfig", + "event": [ + { + "listen": "test", + "script": { + "id": "e440c87c-49ff-4443-b9bf-b44d4e9a480f", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"priceKey\", pm.response.json().key);", + " pm.environment.set(\"priceVersion\", pm.response.json().version);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "dev", + "versions" + ] + } + }, + "response": [] + }, + { + "name": "Update priceConfig", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "{{priceKey}}", + "versions", + "{{priceVersion}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete priceConfig", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "{{priceKey}}", + "versions", + "{{priceVersion}}" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "59182724-4332-4d76-90ea-f7520a7b1be9", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "abc13dca-e8a4-4995-970f-00e5889a5f2d", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Price Config Revision", + "item": [ + { + "name": "List all revision for version", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/dev/versions/3/revisions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "dev", + "versions", + "3", + "revisions" + ] + } + }, + "response": [] + }, + { + "name": "Get a particular revision", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions/{{priceRevision}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "{{priceKey}}", + "versions", + "{{priceVersion}}", + "revisions", + "{{priceRevision}}" + ] + } + }, + "response": [] + }, + { + "name": "Create price config", + "event": [ + { + "listen": "test", + "script": { + "id": "d53ed608-b21c-4d6f-bb68-c2beda1d631d", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"priceRevision\", pm.response.json().revision);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "{{priceKey}}", + "versions", + "{{priceVersion}}", + "revisions" + ] + } + }, + "response": [] + }, + { + "name": "Create for no exist key", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/no-exist-key/versions/1/revisions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "no-exist-key", + "versions", + "1", + "revisions" + ] + } + }, + "response": [] + }, + { + "name": "Delete revision", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions/{{priceRevision}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "priceConfig", + "{{priceKey}}", + "versions", + "{{priceVersion}}", + "revisions", + "{{priceRevision}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Plan Config Version", + "item": [ + { + "name": "List plan configs", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/dev/versions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "dev", + "versions" + ] + } + }, + "response": [] + }, + { + "name": "Get a particular version", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "{{planKey}}", + "versions", + "{{planVersion}}" + ] + } + }, + "response": [] + }, + { + "name": "Get latest version", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "{{planKey}}" + ] + } + }, + "response": [] + }, + { + "name": "Create plan config", + "event": [ + { + "listen": "test", + "script": { + "id": "97bc350a-0c4f-46a6-a315-a62b203b3ad2", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"planKey\", pm.response.json().key);", + " pm.environment.set(\"planVersion\", pm.response.json().version);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/dev/versions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "dev", + "versions" + ] + } + }, + "response": [] + }, + { + "name": "Update plan config", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "{{planKey}}", + "versions", + "{{planVersion}}" + ] + } + }, + "response": [] + }, + { + "name": "Delete plan config", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "{{planKey}}", + "versions", + "{{planVersion}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Plan Config Revision", + "item": [ + { + "name": "List all revision for version", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "{{planKey}}", + "versions", + "{{planVersion}}", + "revisions" + ] + } + }, + "response": [] + }, + { + "name": "Get a particular revision", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions/{{planRevision}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "{{planKey}}", + "versions", + "{{planVersion}}", + "revisions", + "{{planRevision}}" + ] + } + }, + "response": [] + }, + { + "name": "Create plan config", + "event": [ + { + "listen": "test", + "script": { + "id": "a5373f1f-4beb-46f9-8538-10c938c204ba", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"planRevision\", pm.response.json().revision);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "{{planKey}}", + "versions", + "{{planVersion}}", + "revisions" + ] + } + }, + "response": [] + }, + { + "name": "Create for no exist key", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/no-exist-key/versions/1/revisions", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "no-exist-key", + "versions", + "1", + "revisions" + ] + } + }, + "response": [] + }, + { + "name": "Delete revision", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions/{{planRevision}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "metadata", + "planConfig", + "{{planKey}}", + "versions", + "{{planVersion}}", + "revisions", + "{{planRevision}}" + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Project Reports", + "item": [ + { + "name": "summary", + "item": [ + { + "name": "get report by admin", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}", + "type": "text" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/reports?reportName=summary", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "reports" + ], + "query": [ + { + "key": "reportName", + "value": "summary" + } + ] + } + }, + "response": [] + }, + { + "name": "get report by member", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{jwt-token-member2-40051335}}", + "type": "text" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/reports?reportName=summary", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "reports" + ], + "query": [ + { + "key": "reportName", + "value": "summary" + } + ] + } + }, + "response": [] + }, + { + "name": "get report with invalid project id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/123456/reports?reportName=summary", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "123456", + "reports" + ], + "query": [ + { + "key": "reportName", + "value": "summary" + } + ] + } + }, + "response": [] + }, + { + "name": "get report with invalid report name", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/reports?reportName=summary123", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "reports" + ], + "query": [ + { + "key": "reportName", + "value": "summary123" + } + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true + }, + { + "name": "projectBudget", + "item": [ + { + "name": "get report by admin", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "type": "text", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/reports?reportName=projectBudget", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "reports" + ], + "query": [ + { + "key": "reportName", + "value": "projectBudget" + } + ] + } + }, + "response": [] + } + ], + "protocolProfileBehavior": {}, + "_postman_isSubFolder": true + } + ], + "protocolProfileBehavior": {} + }, + { + "name": "Project Setting", + "item": [ + { + "name": "Create project setting - double", + "event": [ + { + "listen": "test", + "script": { + "id": "7350de08-5111-44f8-8a4c-3d0c48bcd8d4", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"settingId\", pm.response.json().id);", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"double\",\r\n \"projectId\": 1,\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/settings", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "settings" + ] + } + }, + "response": [] + }, + { + "name": "Create project setting - percentage", + "event": [ + { + "listen": "test", + "script": { + "id": "bf3aa19f-517c-4103-9250-82d7847e7477", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"key\": \"markup_fee\",\r\n \"value\": \"18.88\",\r\n \"valueType\": \"percentage\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \"topcoderRoles\": [\"Connect Copilot\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/settings", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "settings" + ] + } + }, + "response": [] + }, + { + "name": "Create project setting - for project = 2", + "event": [ + { + "listen": "test", + "script": { + "id": "7350de08-5111-44f8-8a4c-3d0c48bcd8d4", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"2222\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}" + }, + "url": { + "raw": "{{api-url}}/projects/2/settings", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "2", + "settings" + ] + } + }, + "response": [] + }, + { + "name": "Create project setting - another estimation type", + "event": [ + { + "listen": "test", + "script": { + "id": "7350de08-5111-44f8-8a4c-3d0c48bcd8d4", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"key\": \"markup_reference_program\",\r\n \"value\": \"17800\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\", \"copilot\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}" + }, + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/settings", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "settings" + ] + } + }, + "response": [] + }, + { + "name": "Create project setting with non estimation type", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"key\": \"markup_non_estimation\",\r\n \"value\": \"8765\",\r\n \"valueType\": \"string\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t \"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions/{{priceRevision}}", + "raw": "{{api-url}}/projects/{{projectId}}/settings", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "priceConfig", - "{{priceKey}}", - "versions", - "{{priceVersion}}", - "revisions", - "{{priceRevision}}" + "{{projectId}}", + "settings" ] } }, "response": [] }, { - "name": "Create price config", + "name": "Create project setting - duplicate key", "event": [ { "listen": "test", "script": { - "id": "d53ed608-b21c-4d6f-bb68-c2beda1d631d", + "id": "7350de08-5111-44f8-8a4c-3d0c48bcd8d4", "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"priceRevision\", pm.response.json().revision);", - "});" + "" ], "type": "text/javascript" } } ], "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, "method": "POST", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions", + "raw": "{{api-url}}/projects/{{projectId}}/settings", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "priceConfig", - "{{priceKey}}", - "versions", - "{{priceVersion}}", - "revisions" + "{{projectId}}", + "settings" ] } }, "response": [] }, { - "name": "Create for no exist key", + "name": "Create project setting with invalid valueType", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, "method": "POST", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"int1\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/no-exist-key/versions/1/revisions", + "raw": "{{api-url}}/projects/{{projectId}}/settings", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "priceConfig", - "no-exist-key", - "versions", - "1", - "revisions" + "{{projectId}}", + "settings" ] } }, "response": [] }, { - "name": "Delete revision", + "name": "Create project setting with invalid percentage value", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "DELETE", + "method": "POST", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\r\n \"key\": \"markup_community\",\r\n \"value\": \"200\",\r\n \"valueType\": \"percentage\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/priceConfig/{{priceKey}}/versions/{{priceVersion}}/revisions/{{priceRevision}}", + "raw": "{{api-url}}/projects/{{projectId}}/settings", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "priceConfig", - "{{priceKey}}", - "versions", - "{{priceVersion}}", - "revisions", - "{{priceRevision}}" + "{{projectId}}", + "settings" ] } }, "response": [] - } - ] - }, - { - "name": "Plan Config Version", - "item": [ + }, { - "name": "List plan configs", + "name": "Create project setting with missing readPermission", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"int\",\r\n \"projectId\": 1,\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"metadata\": {}\r\n}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/dev/versions", + "raw": "{{api-url}}/projects/{{projectId}}/settings", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "dev", - "versions" + "{{projectId}}", + "settings" ] } }, "response": [] }, { - "name": "Get a particular version", + "name": "Create project setting with empty body", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}", + "raw": "{{api-url}}/projects/{{projectId}}/settings", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "{{planKey}}", - "versions", - "{{planVersion}}" + "{{projectId}}", + "settings" ] } }, "response": [] }, { - "name": "Get latest version", + "name": "Create project setting - not permitted", + "event": [ + { + "listen": "test", + "script": { + "id": "7350de08-5111-44f8-8a4c-3d0c48bcd8d4", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-member-40051331}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"key\": \"markup_topcoder_service\",\r\n \"value\": \"1000\",\r\n \"valueType\": \"double\",\r\n\t\"writePermission\": {\r\n\t \t\"allowRule\": {\r\n\t \t\"projectRoles\": [\"account_manager\"],\r\n\t \t\"topcoderRoles\": [\"administrator\", \"Connect Admin\"]\r\n\t },\r\n\t \t\"denyRule\": {\r\n\t \t\"topcoderRoles\": [\"Connect Copilot Manager\"]\r\n\t }\r\n\t },\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"administrator\", \"Connect Admin\", \"Connect Account Manager\"]\r\n\t },\r\n\t\"metadata\": {}\r\n}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}", + "raw": "{{api-url}}/projects/{{projectId}}/settings", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "{{planKey}}" + "{{projectId}}", + "settings" ] } }, "response": [] }, { - "name": "Create plan config", - "event": [ - { - "listen": "test", - "script": { - "id": "97bc350a-0c4f-46a6-a315-a62b203b3ad2", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"planKey\", pm.response.json().key);", - " pm.environment.set(\"planVersion\", pm.response.json().version);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "List project setting", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/projects/{{projectId}}/settings", + "host": [ + "{{api-url}}" + ], + "path": [ + "projects", + "{{projectId}}", + "settings" ] - }, - "method": "POST", + } + }, + "response": [] + }, + { + "name": "List project setting - 403", + "request": { + "method": "GET", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-copilot-40051332}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" - }, "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/dev/versions", + "raw": "{{api-url}}/projects/{{projectId}}/settings", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "dev", - "versions" + "{{projectId}}", + "settings" ] } }, "response": [] }, { - "name": "Update plan config", + "name": "List project setting - manager", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "PATCH", + "method": "GET", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-manager-40051334}}" } ], - "body": { - "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test111\"\r\n \t}\r\n }" - }, "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}", + "raw": "{{api-url}}/projects/{{projectId}}/settings", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "{{planKey}}", - "versions", - "{{planVersion}}" + "{{projectId}}", + "settings" ] } }, "response": [] }, { - "name": "Delete plan config", + "name": "Update project setting - (failed) change key", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "DELETE", + "method": "PATCH", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" } ], "body": { "mode": "raw", - "raw": "" + "raw": "{\r\n \"key\": \"markup_community\",\r\n\t\"readPermission\": {\r\n\t \t\"projectRoles\": [\"manager\"],\r\n\t \"topcoderRoles\": [\"Connect Manager\"]\r\n\t }\r\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}", + "raw": "{{api-url}}/projects/{{projectId}}/settings/{{settingId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "{{planKey}}", - "versions", - "{{planVersion}}" + "{{projectId}}", + "settings", + "{{settingId}}" ] } }, "response": [] - } - ] - }, - { - "name": "Plan Config Revision", - "item": [ + }, { - "name": "List all revision for version", + "name": "Update project setting - change double to percentage", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"value\": \"35.60\",\r\n \"valueType\": \"percentage\"\r\n}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions", + "raw": "{{api-url}}/projects/{{projectId}}/settings/{{settingId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "{{planKey}}", - "versions", - "{{planVersion}}", - "revisions" + "{{projectId}}", + "settings", + "{{settingId}}" ] } }, "response": [] }, { - "name": "Get a particular revision", + "name": "Update project setting - non-existent project", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"value\": \"30\",\r\n \"valueType\": \"percentage\"\r\n}" }, - "method": "GET", - "header": [], "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions/{{planRevision}}", + "raw": "{{api-url}}/projects/9999/settings/{{settingId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "{{planKey}}", - "versions", - "{{planVersion}}", - "revisions", - "{{planRevision}}" + "9999", + "settings", + "{{settingId}}" ] } }, "response": [] }, { - "name": "Create plan config", - "event": [ - { - "listen": "test", - "script": { - "id": "a5373f1f-4beb-46f9-8538-10c938c204ba", - "exec": [ - "pm.test(\"Status code is 201\", function () {", - " pm.response.to.have.status(201);", - " pm.environment.set(\"planRevision\", pm.response.json().revision);", - "});" - ], - "type": "text/javascript" - } - } - ], + "name": "Update project setting - non-existent project setting", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + "raw": "{\r\n \"value\": \"30\",\r\n \"valueType\": \"percentage\"\r\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions", + "raw": "{{api-url}}/projects/{{projectId}}/settings/9999", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "{{planKey}}", - "versions", - "{{planVersion}}", - "revisions" + "{{projectId}}", + "settings", + "9999" ] } }, "response": [] }, { - "name": "Create for no exist key", + "name": "Update project setting - change readPermission", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token-admin-40051333}}" } ], "body": { "mode": "raw", - "raw": "{\r\n \t\"config\": {\r\n \t\t\"hello\": \"test\"\r\n \t}\r\n }" + "raw": "{\r\n\t\"readPermission\": {\r\n\t\t\"projectRoles\": [\"manager\"],\r\n\t\t\"topcoderRoles\": [\"Connect Manager\"]\r\n\t}\r\n}" }, "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/no-exist-key/versions/1/revisions", + "raw": "{{api-url}}/projects/{{projectId}}/settings/{{settingId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "no-exist-key", - "versions", - "1", - "revisions" + "{{projectId}}", + "settings", + "{{settingId}}" ] } }, "response": [] }, { - "name": "Delete revision", + "name": "Delete project setting", "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw", - "type": "string" - } - ] - }, "method": "DELETE", "header": [ { "key": "Content-Type", - "name": "Content-Type", - "type": "text", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" } ], "body": { @@ -7198,25 +10421,23 @@ "raw": "" }, "url": { - "raw": "{{api-url}}/projects/metadata/planConfig/{{planKey}}/versions/{{planVersion}}/revisions/{{planRevision}}", + "raw": "{{api-url}}/projects/{{projectId}}/settings/{{settingId}}", "host": [ "{{api-url}}" ], "path": [ "projects", - "metadata", - "planConfig", - "{{planKey}}", - "versions", - "{{planVersion}}", - "revisions", - "{{planRevision}}" + "{{projectId}}", + "settings", + "{{settingId}}" ] } }, "response": [] } - ] + ], + "protocolProfileBehavior": {} } - ] -} + ], + "protocolProfileBehavior": {} +} \ No newline at end of file From 2033fabcede4641ea0c257d6ca5a2ff5fde49fdf Mon Sep 17 00:00:00 2001 From: Jan Popieluch Date: Sat, 26 Oct 2019 12:31:37 +0200 Subject: [PATCH 17/88] Merge with #1cee66ef --- docs/swagger.yaml | 1320 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1316 insertions(+), 4 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index b653e9d8..6ca77135 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -804,6 +804,726 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/workstreams': + parameters: + - $ref: '#/parameters/projectIdParam' + get: + tags: + - workstream + operationId: findWorkStreams + security: + - Bearer: [] + description: >- + Retrieve all project workstreams. + responses: + '200': + description: A list of project work streams + schema: + type: array + items: + $ref: '#/definitions/WorkStream' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + post: + tags: + - workstream + operationId: addWorkStream + security: + - Bearer: [] + description: >- + Create a work stream. + parameters: + - in: body + name: body + required: true + schema: + type: object + allOf: + - $ref: '#/definitions/WorkStreamRequest' + responses: + '200': + description: Returns the newly created project work stream + schema: + $ref: '#/definitions/WorkStream' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/workstreams/{workStreamId}': + parameters: + - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/workStreamIdParam' + get: + tags: + - workstream + description: >- + Retrieve work stream by id. + security: + - Bearer: [] + responses: + '200': + description: a project work stream + schema: + $ref: '#/definitions/WorkStream' + '401': + description: Unauthorized + 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: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/workStreamIdParam' + operationId: getWorkStream + patch: + tags: + - workstream + operationId: updateWorkStream + security: + - Bearer: [] + description: >- + Update a project work stream. + responses: + '200': + description: Successfully updated project work stream. + schema: + $ref: '#/definitions/WorkStream' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + 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: + $ref: '#/definitions/ErrorModel' + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/WorkStreamRequest' + delete: + tags: + - workstream + description: >- + Remove an existing project work stream. + security: + - Bearer: [] + responses: + '204': + description: Project work stream successfully removed + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/workstreams/{workStreamId}/works': + parameters: + - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/workStreamIdParam' + get: + tags: + - work + operationId: findWorks + security: + - Bearer: [] + description: >- + Retrieve all works for given project and workstream. + parameters: + - name: fields + required: false + type: string + in: query + description: | + Comma separated list of project phase fields to return. + - name: sort + required: false + description: > + sort project phases by startDate, endDate, status, order. Default is + startDate asc + in: query + type: string + responses: + '200': + description: A list of project works + schema: + type: array + items: + $ref: '#/definitions/ProjectPhase' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project or workstream is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + post: + tags: + - work + operationId: addWork + security: + - Bearer: [] + description: >- + Create a work + parameters: + - in: body + name: body + required: true + schema: + type: object + allOf: + - $ref: '#/definitions/ProjectPhaseRequest' + responses: + '200': + description: Returns the newly created project work + schema: + $ref: '#/definitions/ProjectPhase' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project or workstream is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/workstreams/{workStreamId}/works/{phaseId}': + parameters: + - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/phaseIdParam' + - $ref: '#/parameters/workStreamIdParam' + get: + tags: + - work + description: >- + Retrieve work by id. + security: + - Bearer: [] + responses: + '200': + description: a project work + schema: + $ref: '#/definitions/ProjectPhase' + '401': + description: Unauthorized + 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: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/phaseIdParam' + operationId: getWork + patch: + tags: + - work + operationId: updateWork + security: + - Bearer: [] + description: >- + Update work for given project and workstream. + responses: + '200': + description: Successfully updated project work. + schema: + $ref: '#/definitions/ProjectPhase' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + 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: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/phaseIdParam' + - name: body + in: body + required: true + schema: + $ref: '#/definitions/ProjectPhaseRequest' + delete: + tags: + - work + description: >- + Remove an existing work by id for given project and workstream. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/phaseIdParam' + responses: + '204': + description: Work successfully removed + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project or workstream is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/workstreams/{workStreamId}/works/{phaseId}/workitems': + parameters: + - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/phaseIdParam' + - $ref: '#/parameters/workStreamIdParam' + get: + tags: + - work item + operationId: findWorkItems + security: + - Bearer: [] + description: >- + Retrieve all work items for given project, workstream and phase. + responses: + '200': + description: A list of work items + schema: + type: array + items: + $ref: '#/definitions/PhaseProduct' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project, workstream or phase is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + post: + tags: + - work item + operationId: addWorkItem + security: + - Bearer: [] + description: Create a work item for given project, workstream and phase. + parameters: + - in: body + name: body + required: true + schema: + $ref: '#/definitions/PhaseProductRequest' + responses: + '200': + description: Returns the newly created work item + schema: + $ref: '#/definitions/PhaseProduct' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project, workstream or phase is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/workstreams/{workStreamId}/works/{phaseId}/workitems/{productId}': + parameters: + - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/phaseIdParam' + - $ref: '#/parameters/workStreamIdParam' + - $ref: '#/parameters/productIdParam' + get: + tags: + - work item + description: >- + Retrieve work item by id for given project, workstream and phase. + security: + - Bearer: [] + responses: + '200': + description: a work item + schema: + $ref: '#/definitions/PhaseProduct' + '401': + description: Unauthorized + 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: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/phaseIdParam' + operationId: getWorkItem + patch: + tags: + - work item + operationId: updateWorkItem + security: + - Bearer: [] + description: >- + Update a work item for given project, workstream and phase. + responses: + '200': + description: Successfully updated work item. + schema: + $ref: '#/definitions/PhaseProduct' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + 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: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/phaseIdParam' + - name: body + in: body + required: true + schema: + $ref: '#/definitions/PhaseProductRequest' + delete: + tags: + - work item + description: >- + Remove an existing work item by id for given project, workstream and phase. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/phaseIdParam' + responses: + '204': + description: Work item successfully removed + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project, workstream or phase is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/settings': + parameters: + - $ref: '#/parameters/projectIdParam' + get: + tags: + - project settings + operationId: findProjectSettings + security: + - Bearer: [] + description: >- + Retrieve all project settings. Only users with readPermission can get the setting + responses: + '200': + description: A list of project phases + schema: + type: array + items: + $ref: '#/definitions/ProjectSetting' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + post: + tags: + - project settings + operationId: addProjectSetting + security: + - Bearer: [] + description: >- + Create a project setting and create project estimation items based on estimation type. + parameters: + - in: body + name: body + required: true + schema: + type: object + allOf: + - $ref: '#/definitions/ProjectSettingRequest' + responses: + '200': + description: Returns the newly created project phase + schema: + $ref: '#/definitions/ProjectPhase' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/settings/{settingId}': + parameters: + - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/settingIdParam' + patch: + tags: + - project settings + operationId: updateProjectSetting + security: + - Bearer: [] + description: >- + Update a project setting. All user with write permission can edit the setting. + responses: + '200': + description: Successfully updated project setting. + schema: + $ref: '#/definitions/ProjectSetting' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + 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: + $ref: '#/definitions/ErrorModel' + parameters: + - name: body + in: body + required: true + schema: + $ref: '#/definitions/ProjectSettingRequest' + delete: + tags: + - project settings + description: >- + Remove an existing project setting. All users who are connect managers and admins + access this endpoint. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/settingIdParam' + responses: + '204': + description: Project setting successfully removed + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If project is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/projects/{projectId}/estimations/{estimationId}/items': + get: + tags: + - Project Estimation Items + security: + - Bearer: [] + description: get project estimation items + parameters: + - $ref: '#/parameters/projectIdParam' + - $ref: '#/parameters/projectEstimationIdParam' + responses: + '200': + description: List of project estimation items + schema: + type: array + items: + $ref: '#/definitions/ProjectEstimationItem' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: Model not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Invalid server state or unknown error + schema: + $ref: '#/definitions/ErrorModel' '/projects/{projectId}/phases/{phaseId}/products': parameters: - $ref: '#/parameters/projectIdParam' @@ -1960,7 +2680,233 @@ paths: '400': description: Bad request schema: - $ref: '#/definitions/ErrorModel' + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If organization config is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + /projects/metadata/workManagementPermission: + get: + tags: + - workManagementPermission + operationId: findWorkManagementPermissions + security: + - Bearer: [] + description: >- + Retrieve all work management permissions. Only admin or connect admin can access + this endpoint. + parameters: + - name: filter + required: true + type: string + in: query + description: | + Url encoded list of Supported filters + - projectTemplateId (required) + responses: + '200': + description: A list of work management permissions + schema: + type: array + items: + $ref: '#/definitions/WorkManagementPermission' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + post: + tags: + - workManagementPermission + operationId: addWorkManagementPermission + security: + - Bearer: [] + description: >- + Create a work management permission. Only admin or connect admin can access + this endpoint. + parameters: + - in: body + name: body + required: true + schema: + $ref: '#/definitions/WorkManagementPermissionCreateRequest' + responses: + '200': + description: Returns the newly created work management permission + schema: + $ref: '#/definitions/WorkManagementPermission' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + '/projects/metadata/workManagementPermission/{id}': + get: + tags: + - workManagementPermission + description: Retrieve work management permission by id. Only admin or connect admin can access + this endpoint. + security: + - Bearer: [] + responses: + '200': + description: a project type + schema: + $ref: '#/definitions/WorkManagementPermission' + '401': + description: Unauthorized + 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: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/permissionIdParam' + operationId: getWorkManagementPermission + patch: + tags: + - workManagementPermission + operationId: updateWorkManagementPermission + security: + - Bearer: [] + description: >- + Update a work management permission. Only admin or connect admin can access + this endpoint. + responses: + '200': + description: Successfully updated work management permission. + schema: + $ref: '#/definitions/WorkManagementPermission' + '400': + description: Bad request + schema: + $ref: '#/definitions/ErrorModel' + '401': + description: Unauthorized + 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: + $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/permissionIdParam' + - name: body + in: body + required: true + schema: + $ref: '#/definitions/WorkManagementPermissionCreateRequest' + delete: + tags: + - workManagementPermission + description: >- + Remove an existing work management permission. Only admin or connect admin can + access this endpoint. + security: + - Bearer: [] + parameters: + - $ref: '#/parameters/permissionIdParam' + responses: + '204': + description: Work management permission successfully removed + '401': + description: Unauthorized + schema: + $ref: '#/definitions/ErrorModel' + '403': + description: Forbidden + schema: + $ref: '#/definitions/ErrorModel' + '404': + description: If work management permission is not found + schema: + $ref: '#/definitions/ErrorModel' + '500': + description: Internal Server Error + schema: + $ref: '#/definitions/ErrorModel' + + '/projects/{projectId}/permissions': + get: + tags: + - permissions + description: Retrieve permissions. + security: + - Bearer: [] + responses: + '200': + description: permissions + schema: + title: Single work management permission response object + type: object + properties: + id: + type: string + description: unique id identifying the request + version: + type: string + result: + type: object + properties: + success: + type: boolean + status: + type: integer + format: int32 + description: http status code + metadata: + $ref: '#/definitions/ResponseMetadata' + content: + type: object + example: + 'work.create': true '401': description: Unauthorized schema: @@ -1970,13 +2916,16 @@ paths: schema: $ref: '#/definitions/ErrorModel' '404': - description: If organization config is not found + description: Not found schema: $ref: '#/definitions/ErrorModel' '500': description: Internal Server Error schema: $ref: '#/definitions/ErrorModel' + parameters: + - $ref: '#/parameters/projectIdParam' + operationId: getPermissions /timelines: get: tags: @@ -3660,6 +4609,22 @@ parameters: type: integer format: int64 minimum: 1 + workStreamIdParam: + name: workStreamId + in: path + description: work stream identifier + required: true + type: integer + format: int64 + minimum: 1 + settingIdParam: + name: settingId + in: path + description: project setting identifier + required: true + type: integer + format: int64 + minimum: 1 productIdParam: name: productId in: path @@ -3721,6 +4686,13 @@ parameters: required: true type: integer format: int64 + permissionIdParam: + name: id + in: path + description: work management permission id + required: true + type: integer + format: int64 pageParam: name: page in: query @@ -3824,6 +4796,13 @@ parameters: description: manager filter required: false type: string + projectEstimationIdParam: + name: estimationId + in: path + description: project estimation identifier + required: true + type: integer + format: int64 definitions: ResponseMetadata: title: Metadata object for a response @@ -3902,7 +4881,7 @@ definitions: - buildingBlockKey properties: conditions: - type: string + type: string price: type: number format: float @@ -3915,7 +4894,7 @@ definitions: metadata: type: object buildingBlockKey: - type: string + type: string type: type: string description: project type @@ -4476,6 +5455,12 @@ definitions: name: type: string description: the project phase name + description: + type: string + description: the project phase short description + requirements: + type: string + description: the project phase requirements status: type: string description: the project phase status @@ -4954,12 +5939,16 @@ definitions: blockedText: type: string description: the milestone blocked text + statusComment: + type: string + description: the milestone status history comment Milestone: title: Milestone object allOf: - type: object required: - id + - statusHistory - createdAt - createdBy - updatedAt @@ -4969,6 +5958,8 @@ definitions: type: number format: int64 description: the id + statusHistory: + $ref: '#/definitions/StatusHistory' createdAt: type: string description: Datetime (GMT) when object was created @@ -5117,6 +6108,10 @@ definitions: type: array items: $ref: '#/definitions/ProductCategory' + buildingBlocks: + type: array + items: + $ref: '#/definitions/BuildingBlock' ProjectMemberInvite: type: object properties: @@ -5352,3 +6347,320 @@ definitions: config: description: config json type: object + ProjectSetting: + title: Project setting object + allOf: + - type: object + required: + - id + - createdAt + - createdBy + - updatedAt + - updatedBy + properties: + id: + type: number + format: int64 + description: the id + createdAt: + type: string + description: Datetime (GMT) when object was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this object + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when object was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this object + readOnly: true + - $ref: '#/definitions/ProjectSettingRequest' + ProjectSettingRequest: + title: Project setting request object + type: object + required: + - key + - value + - valueType + - readPermission + - writePermission + - metadata + properties: + key: + type: string + description: the project setting key + value: + type: string + description: the project setting value + valueType: + type: string + description: the project setting value type + readPermission: + type: object + description: the project setting read Permission + writePermission: + type: object + description: the project setting write Permission + metadata: + type: object + description: the project setting metadata + WorkStream: + title: Work stream object + allOf: + - type: object + required: + - id + - createdAt + - createdBy + - updatedAt + - updatedBy + properties: + id: + type: number + format: int64 + description: the id + projectId: + type: number + format: int64 + description: the project id + createdAt: + type: string + description: Datetime (GMT) when object was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this object + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when object was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this object + readOnly: true + - $ref: '#/definitions/WorkStreamRequest' + WorkStreamRequest: + title: Work stream request object + type: object + required: + - name + - status + - type + properties: + name: + type: string + description: the work stream name + status: + type: string + description: the work stream status + type: + type: string + description: the type + WorkManagementPermissionCreateRequest: + title: Work Management Permission request object + type: object + required: + - policy + - permission + - projectTemplateId + properties: + policy: + type: string + description: the policy + permission: + type: object + description: the permission + projectTemplateId: + type: number + format: int64 + description: the template id + WorkManagementPermission: + title: Work Management Permission object + allOf: + - type: object + required: + - id + - policy + - permission + - projectTemplateId + - createdAt + - createdBy + - updatedAt + - updatedBy + properties: + id: + type: number + format: int64 + description: the id + policy: + type: string + description: the policy + permission: + type: object + description: the permission + projectTemplateId: + type: number + format: int64 + description: the template id + createdAt: + type: string + description: Datetime (GMT) when object was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this object + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when object was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this object + readOnly: true + - $ref: '#/definitions/WorkManagementPermissionCreateRequest' + StatusHistory: + title: Status history object + type: object + required: + - id + - status + - reference + - referenceId + - comment + properties: + id: + type: string + description: the id + status: + type: string + description: the status + reference: + type: string + description: the referenced model + referenceId: + type: string + description: the referenced id + comment: + type: string + description: the comment + createdAt: + type: string + description: Datetime (GMT) when object was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this object + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when object was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this object + readOnly: true + BuildingBlock: + title: BuildingBlock object + type: object + required: + - id + - key + - config + properties: + id: + type: integer + format: int64 + description: the id + key: + type: string + description: building block key. Unique field. + config: + type: object + description: building block config + createdAt: + type: string + description: Datetime (GMT) when object was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this object + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when object was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this object + readOnly: true + ProjectEstimationItem: + title: ProjectEstimationItem object + type: object + required: + - id + - projectEstimationId + - price + - type + - markupUsedReference + - markupUsedReferenceId + - metadata + properties: + id: + type: integer + format: int64 + description: the id + projectEstimationId: + type: integer + format: int64 + description: the ProjectEstimation id + price: + type: number + format: float + description: the price of this estimation item + type: + type: string + description: the type of this estimation + markupUsedReference: + type: string + description: the reference type of this estimation. Can be "buildingBlock" for example + markupUsedReferenceId: + type: integer + format: int64 + description: the reference object id + metadata: + type: object + description: the metadata of this item + createdAt: + type: string + description: Datetime (GMT) when object was created + readOnly: true + createdBy: + type: integer + format: int64 + description: READ-ONLY. User who created this object + readOnly: true + updatedAt: + type: string + description: READ-ONLY. Datetime (GMT) when object was updated + readOnly: true + updatedBy: + type: integer + format: int64 + description: READ-ONLY. User that last updated this object + readOnly: true \ No newline at end of file From 227023cef707ca595216c093ff9b2c38e4185e3e Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Sun, 27 Oct 2019 11:45:35 +0800 Subject: [PATCH 18/88] feat: updated postman - removed legacy DB endpoints - removed degenerated cases for checking body without "param" - added request to get a timeline by id from DB --- docs/Project API.postman_collection.json | 503 ++--------------------- 1 file changed, 30 insertions(+), 473 deletions(-) diff --git a/docs/Project API.postman_collection.json b/docs/Project API.postman_collection.json index 449b221f..0c484294 100644 --- a/docs/Project API.postman_collection.json +++ b/docs/Project API.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "bd828203-8def-4648-bb54-e509c177ec71", + "_postman_id": "dd9afefd-0b47-4483-a4e5-33ca395791a3", "name": "Project API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -1426,177 +1426,6 @@ }, "response": [] }, - { - "name": "List projects DB", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ] - }, - "description": "List all the project with no filter. Default sort and limits are applied." - }, - "response": [] - }, - { - "name": "List projects DB with limit and offset", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db?limit=1&offset=1", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ], - "query": [ - { - "key": "limit", - "value": "1" - }, - { - "key": "offset", - "value": "1" - } - ] - }, - "description": "List all the project with no filter. Limit of 1 and offset of 1 is applied" - }, - "response": [] - }, - { - "name": "List projects DB with filters applied", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db?filter=type%3Dgeneric", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ], - "query": [ - { - "key": "filter", - "value": "type%3Dgeneric" - } - ] - }, - "description": "List all the project with filters applied. The filter string should be url encoded. Default limit and offset is applicable" - }, - "response": [] - }, - { - "name": "List projects DB with sort applied", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db?sort=type%20desc", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ], - "query": [ - { - "key": "sort", - "value": "type%20desc" - } - ] - }, - "description": "List all the project with custom sort and no filter. Default sort and limits are applied. The sort string has to be url encoded. Sort is of type `key asc|desc`" - }, - "response": [] - }, - { - "name": "List projects DB and return specific fields", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db?fields=id,name,description", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ], - "query": [ - { - "key": "fields", - "value": "id,name,description" - } - ] - }, - "description": "List all the project with no filter. Default sort and limits are applied. The fields to return is specified as comma separated list. Only those fields should be returned." - }, - "response": [] - }, - { - "name": "List projects DB with copilot token", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token-copilot-40051332}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/db", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "db" - ] - } - }, - "response": [] - }, { "name": "List projects", "request": { @@ -2372,39 +2201,6 @@ }, "response": [] }, - { - "name": "Create workstream without valid name", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n}" - }, - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/workstreams", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "workstreams" - ] - }, - "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." - }, - "response": [] - }, { "name": "Create workstream with valid values", "event": [ @@ -2643,41 +2439,6 @@ }, "response": [] }, - { - "name": "Create work without valid name", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n}" - }, - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "workstreams", - "{{workStreamId}}", - "works" - ] - }, - "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." - }, - "response": [] - }, { "name": "Create work with valid values", "event": [ @@ -3064,43 +2825,6 @@ }, "response": [] }, - { - "name": "Create work item without valid name", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n}" - }, - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/workstreams/{{workStreamId}}/works/{{workId}}/workitems", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "workstreams", - "{{workStreamId}}", - "works", - "{{workId}}", - "workitems" - ] - }, - "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." - }, - "response": [] - }, { "name": "Create work item with valid values", "event": [ @@ -3361,40 +3085,7 @@ "response": [] }, { - "name": "Create work management permission without valid name", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n}" - }, - "url": { - "raw": "{{api-url}}/projects/metadata/workManagementPermission", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "metadata", - "workManagementPermission" - ] - }, - "description": "Certain fields are mandatory while creating project. If invalid request body is supplied this should return 422 status code." - }, - "response": [] - }, - { - "name": "Create work management permission with valid values", + "name": "Create work management permission with valid values 1", "event": [ { "listen": "test", @@ -3442,7 +3133,7 @@ "response": [] }, { - "name": "Create work management permission with valid values", + "name": "Create work management permission with valid values 2", "event": [ { "listen": "test", @@ -4638,140 +4329,6 @@ }, "response": [] }, - { - "name": "List Phase DB", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/db", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "db" - ] - } - }, - "response": [] - }, - { - "name": "List Phase DB with fields", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/db?fields=status,name,budget", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "db" - ], - "query": [ - { - "key": "fields", - "value": "status,name,budget" - } - ] - } - }, - "response": [] - }, - { - "name": "List Phase DB with sort", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/db?sort=status desc", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "db" - ], - "query": [ - { - "key": "sort", - "value": "status desc" - } - ] - } - }, - "response": [] - }, - { - "name": "List Phase DB with sort by order", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/db?sort=order desc", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "db" - ], - "query": [ - { - "key": "sort", - "value": "order desc" - } - ] - } - }, - "response": [] - }, { "name": "List Phase", "request": { @@ -5085,33 +4642,6 @@ }, "response": [] }, - { - "name": "List Phase DB Products", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{jwt-token}}" - } - ], - "url": { - "raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}/products/db", - "host": [ - "{{api-url}}" - ], - "path": [ - "projects", - "{{projectId}}", - "phases", - "{{phaseId}}", - "products", - "db" - ] - } - }, - "response": [] - }, { "name": "List Phase Products", "request": { @@ -6842,6 +6372,33 @@ }, "response": [] }, + { + "name": "Get timeline from DB", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{jwt-token}}" + } + ], + "url": { + "raw": "{{api-url}}/timelines/{{timelineId}}", + "host": [ + "{{api-url}}" + ], + "path": [ + "timelines", + "{{timelineId}}" + ] + } + }, + "response": [] + }, { "name": "Update timeline", "request": { From 41fd6a14a3f058296f002b10f53fe4b66d0f1a8d Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 28 Oct 2019 17:58:39 +0800 Subject: [PATCH 19/88] feat: Kafka events for Notification Service brought back from "dev" - bring back firing Kafka events for Notification Service - bring back unit tests for these events - added some new unit tests for events - fixed and improved some unit tests for events in "v5-upgrade" and "dev" --- src/constants.js | 86 +- src/events/busApi.js | 809 +++++++++++++++++- src/events/index.js | 22 +- src/events/milestones/index.js | 16 +- src/routes/attachments/create.spec.js | 36 +- src/routes/attachments/delete.js | 1 - src/routes/attachments/delete.spec.js | 25 +- src/routes/attachments/update.spec.js | 28 +- src/routes/milestones/create.js | 9 +- src/routes/milestones/create.spec.js | 40 +- src/routes/milestones/delete.spec.js | 25 +- src/routes/milestones/update.js | 11 +- src/routes/milestones/update.spec.js | 83 +- src/routes/phaseProducts/create.spec.js | 10 +- src/routes/phaseProducts/delete.spec.js | 10 +- src/routes/phaseProducts/update.js | 6 +- src/routes/phaseProducts/update.spec.js | 110 ++- src/routes/phases/create.js | 5 + src/routes/phases/create.spec.js | 39 +- src/routes/phases/delete.spec.js | 26 +- src/routes/phases/update.js | 10 +- src/routes/phases/update.spec.js | 309 ++++++- src/routes/projectMemberInvites/create.js | 43 +- .../projectMemberInvites/create.spec.js | 62 +- .../projectMemberInvites/update.spec.js | 69 +- src/routes/projectMembers/create.js | 36 +- src/routes/projectMembers/create.spec.js | 162 +++- src/routes/projectMembers/delete.js | 2 +- src/routes/projectMembers/delete.spec.js | 52 +- src/routes/projectMembers/update.js | 3 +- src/routes/projectMembers/update.spec.js | 32 +- src/routes/projects/update.spec.js | 187 ++-- src/routes/timelines/update.js | 3 +- src/routes/timelines/update.spec.js | 25 +- src/routes/workItems/create.js | 10 +- src/routes/workItems/create.spec.js | 12 +- src/routes/workItems/delete.js | 10 +- src/routes/workItems/delete.spec.js | 12 +- src/routes/workItems/update.js | 6 +- src/routes/workItems/update.spec.js | 280 +++--- src/routes/works/create.spec.js | 29 +- src/routes/works/delete.js | 12 +- src/routes/works/delete.spec.js | 24 +- src/routes/works/update.js | 13 +- src/routes/works/update.spec.js | 380 +++++++- src/services/busApi.js | 1 + src/util.js | 30 +- 47 files changed, 2706 insertions(+), 505 deletions(-) diff --git a/src/constants.js b/src/constants.js index 7cf68b0d..8b66db6f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -161,12 +161,6 @@ export const BUS_API_EVENT = { MILESTONE_TEMPLATE_REMOVED: 'project.notification.delete', MILESTONE_TEMPLATE_UPDATED: 'project.notification.update', - // TC Message Service events - TOPIC_CREATED: 'notifications.connect.project.topic.created', - TOPIC_UPDATED: 'notifications.connect.project.topic.updated', - POST_CREATED: 'notifications.connect.project.post.created', - POST_UPDATED: 'notifications.connect.project.post.edited', - // Project Member Invites PROJECT_MEMBER_INVITE_CREATED: 'project.notification.create', PROJECT_MEMBER_INVITE_UPDATED: 'project.notification.update', @@ -178,6 +172,86 @@ export const BUS_API_EVENT = { PROJECT_METADATA_DELETE: 'project.notification.delete', }; +export const CONNECT_NOTIFICATION_EVENT = { + PROJECT_CREATED: 'connect.notification.project.created', + PROJECT_UPDATED: 'connect.notification.project.updated', + PROJECT_SUBMITTED_FOR_REVIEW: 'connect.notification.project.submittedForReview', + PROJECT_APPROVED: 'connect.notification.project.approved', + PROJECT_PAUSED: 'connect.notification.project.paused', + PROJECT_COMPLETED: 'connect.notification.project.completed', + PROJECT_CANCELED: 'connect.notification.project.canceled', + PROJECT_ACTIVE: 'connect.notification.project.active', + + PROJECT_PHASE_TRANSITION_ACTIVE: 'connect.notification.project.phase.transition.active', + PROJECT_PHASE_TRANSITION_COMPLETED: 'connect.notification.project.phase.transition.completed', + PROJECT_PHASE_UPDATE_PAYMENT: 'connect.notification.project.phase.update.payment', + PROJECT_PHASE_UPDATE_PROGRESS: 'connect.notification.project.phase.update.progress', + PROJECT_PHASE_UPDATE_SCOPE: 'connect.notification.project.phase.update.scope', + + PROJECT_WORK_TRANSITION_ACTIVE: 'connect.notification.project.work.transition.active', + PROJECT_WORK_TRANSITION_COMPLETED: 'connect.notification.project.work.transition.completed', + PROJECT_WORK_UPDATE_PAYMENT: 'connect.notification.project.work.update.payment', + PROJECT_WORK_UPDATE_PROGRESS: 'connect.notification.project.work.update.progress', + PROJECT_WORK_UPDATE_SCOPE: 'connect.notification.project.work.update.scope', + + MEMBER_JOINED: 'connect.notification.project.member.joined', + MEMBER_LEFT: 'connect.notification.project.member.left', + MEMBER_REMOVED: 'connect.notification.project.member.removed', + MEMBER_ASSIGNED_AS_OWNER: 'connect.notification.project.member.assignedAsOwner', + MEMBER_JOINED_COPILOT: 'connect.notification.project.member.copilotJoined', + MEMBER_JOINED_MANAGER: 'connect.notification.project.member.managerJoined', + + PROJECT_LINK_CREATED: 'connect.notification.project.linkCreated', + 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_TEAM_UPDATED: 'connect.notification.project.team.updated', + + // When phase is added/updated/deleted from the project, + // When product is added/deleted from a phase + // When product is updated on any field other than specification + PROJECT_PLAN_UPDATED: 'connect.notification.project.plan.updated', + + PROJECT_PLAN_READY: 'connect.notification.project.plan.ready', + + // When milestone is added/deleted to/from the phase, + // When milestone is updated for duration/startDate/endDate/status + TIMELINE_ADJUSTED: 'connect.notification.project.timeline.adjusted', + + // When specification of a product is modified + PROJECT_PRODUCT_SPECIFICATION_MODIFIED: 'connect.notification.project.product.update.spec', + + // When specification of a work item is modified + PROJECT_WORKITEM_SPECIFICATION_MODIFIED: 'connect.notification.project.workitem.update.spec', + + MILESTONE_ADDED: 'connect.notification.project.timeline.milestone.added', + MILESTONE_REMOVED: 'connect.notification.project.timeline.milestone.removed', + MILESTONE_UPDATED: 'connect.notification.project.timeline.milestone.updated', + // When milestone is marked as active + MILESTONE_TRANSITION_ACTIVE: 'connect.notification.project.timeline.milestone.transition.active', + // When milestone is marked as completed + MILESTONE_TRANSITION_COMPLETED: 'connect.notification.project.timeline.milestone.transition.completed', + // When milestone is marked as paused + MILESTONE_TRANSITION_PAUSED: 'connect.notification.project.timeline.milestone.transition.paused', + // When milestone is waiting for customers's input + MILESTONE_WAITING_CUSTOMER: 'connect.notification.project.timeline.milestone.waiting.customer', + + // Project Member Invites + PROJECT_MEMBER_INVITE_CREATED: 'connect.notification.project.member.invite.created', + PROJECT_MEMBER_INVITE_REQUESTED: 'connect.notification.project.member.invite.requested', + PROJECT_MEMBER_INVITE_UPDATED: 'connect.notification.project.member.invite.updated', + PROJECT_MEMBER_INVITE_APPROVED: 'connect.notification.project.member.invite.approved', + PROJECT_MEMBER_INVITE_REJECTED: 'connect.notification.project.member.invite.rejected', + PROJECT_MEMBER_EMAIL_INVITE_CREATED: 'connect.notification.email.project.member.invite.created', + + // TC Message Service events + TOPIC_CREATED: 'connect.notification.project.topic.created', + TOPIC_UPDATED: 'connect.notification.project.topic.updated', + POST_CREATED: 'connect.notification.project.post.created', + POST_UPDATED: 'connect.notification.project.post.edited', +}; + export const REGEX = { URL: /^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,15})+(\:[0-9]{2,5})?(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=;]*)?$/, // eslint-disable-line }; diff --git a/src/events/busApi.js b/src/events/busApi.js index 1dac2122..bb6e38ae 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -1,7 +1,44 @@ import _ from 'lodash'; +import moment from 'moment'; import config from 'config'; -import { EVENT, BUS_API_EVENT } from '../constants'; +import { + EVENT, + BUS_API_EVENT, + CONNECT_NOTIFICATION_EVENT, + PROJECT_STATUS, + PROJECT_PHASE_STATUS, + PROJECT_MEMBER_ROLE, + ROUTES, + MILESTONE_STATUS, + INVITE_STATUS, +} from '../constants'; import { createEvent } from '../services/busApi'; +import models from '../models'; +import util from '../util'; + +/** + * Map of project status and event name sent to bus api + */ +const mapEventTypes = { + [PROJECT_STATUS.DRAFT]: CONNECT_NOTIFICATION_EVENT.PROJECT_CREATED, + [PROJECT_STATUS.IN_REVIEW]: CONNECT_NOTIFICATION_EVENT.PROJECT_SUBMITTED_FOR_REVIEW, + [PROJECT_STATUS.REVIEWED]: CONNECT_NOTIFICATION_EVENT.PROJECT_APPROVED, + [PROJECT_STATUS.COMPLETED]: CONNECT_NOTIFICATION_EVENT.PROJECT_COMPLETED, + [PROJECT_STATUS.CANCELLED]: CONNECT_NOTIFICATION_EVENT.PROJECT_CANCELED, + [PROJECT_STATUS.PAUSED]: CONNECT_NOTIFICATION_EVENT.PROJECT_PAUSED, + [PROJECT_STATUS.ACTIVE]: CONNECT_NOTIFICATION_EVENT.PROJECT_ACTIVE, +}; + +/** + * Builds the connect project attachment url for the given project and attachment ids. + * + * @param {string|number} projectId the project id + * @param {string|number} attachmentId the attachment id + * @returns {string} the connect project attachment url + */ +function connectProjectAttachmentUrl(projectId, attachmentId) { + return `${config.get('connectProjectsUrl')}${projectId}/attachments/${attachmentId}`; +} /** * Builds the connect project url for the given project id. @@ -17,7 +54,7 @@ module.exports = (app, logger) => { /** * PROJECT_DRAFT_CREATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED, ({ req, project }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED, ({ req, project }) => { logger.debug('receive PROJECT_DRAFT_CREATED event'); // send event to bus api @@ -25,18 +62,82 @@ module.exports = (app, logger) => { refCode: _.get(project, 'details.utm.code'), projectUrl: connectProjectUrl(project.id), }), logger); + + /* + Send event for Notification Service + */ + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_CREATED, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); }); /** * PROJECT_UPDATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_UPDATED, ({ req, updated }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_UPDATED, ({ req, original, updated }) => { logger.debug('receive PROJECT_UPDATED event'); createEvent(BUS_API_EVENT.PROJECT_UPDATED, _.assign(updated, { refCode: _.get(updated, 'details.utm.code'), projectUrl: connectProjectUrl(updated.id), }), logger); + + /* + Send event for Notification Service + */ + if (original.status !== updated.status) { + logger.debug(`project status is updated from ${original.status} to ${updated.status}`); + createEvent(mapEventTypes[updated.status], { + projectId: updated.id, + projectName: updated.name, + refCode: _.get(updated, 'details.utm.code'), + projectUrl: connectProjectUrl(updated.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } else if ( + !_.isEqual(original.details, updated.details) || + !_.isEqual(original.name, updated.name) || + !_.isEqual(original.description, updated.description)) { + logger.debug('project spec is updated'); + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_SPECIFICATION_MODIFIED, { + projectId: updated.id, + projectName: updated.name, + refCode: _.get(updated, 'details.utm.code'), + projectUrl: connectProjectUrl(updated.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } else if (!_.isEqual(original.bookmarks, updated.bookmarks)) { + logger.debug('project bookmarks is updated'); + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_LINK_CREATED, { + projectId: updated.id, + projectName: updated.name, + refCode: _.get(updated, 'details.utm.code'), + projectUrl: connectProjectUrl(updated.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + + // send PROJECT_UPDATED Kafka message when one of the specified below properties changed + const watchProperties = ['status', 'details', 'name', 'description', 'bookmarks']; + if (!_.isEqual(_.pick(original, watchProperties), + _.pick(updated, watchProperties))) { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED, { + projectId: updated.id, + projectName: updated.name, + refCode: _.get(updated, 'details.utm.code'), + projectUrl: connectProjectUrl(updated.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } }); /** @@ -79,84 +180,486 @@ module.exports = (app, logger) => { /** * PROJECT_MEMBER_ADDED */ - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, resource }) => { logger.debug('receive PROJECT_MEMBER_ADDED event'); createEvent(BUS_API_EVENT.PROJECT_MEMBER_ADDED, resource, logger); + + /* + Send event for Notification Service + */ + let eventType; + const member = _.omit(resource, 'resource'); + + if ([ + PROJECT_MEMBER_ROLE.MANAGER, + PROJECT_MEMBER_ROLE.PROJECT_MANAGER, + PROJECT_MEMBER_ROLE.PROGRAM_MANAGER, + PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT, + ].includes(member.role)) { + eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED_MANAGER; + } else if (member.role === PROJECT_MEMBER_ROLE.COPILOT) { + eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED_COPILOT; + } else { + eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED; + } + const projectId = _.parseInt(req.params.projectId); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + createEvent(eventType, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + userId: member.userId, + initiatorUserId: req.authUser.userId, + }, logger); + + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** * PROJECT_MEMBER_REMOVED */ - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, resource }) => { logger.debug('receive PROJECT_MEMBER_REMOVED event'); createEvent(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, resource, logger); + + /* + Send event for Notification Service + */ + let eventType; + const member = _.omit(resource, 'resource'); + if (member.userId === req.authUser.userId) { + eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_LEFT; + } else { + eventType = CONNECT_NOTIFICATION_EVENT.MEMBER_REMOVED; + } + const projectId = _.parseInt(req.params.projectId); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + if (project) { + createEvent(eventType, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + userId: member.userId, + initiatorUserId: req.authUser.userId, + }, logger); + + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** * PROJECT_MEMBER_UPDATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, ({ req, resource, originalResource }) => { logger.debug('receive PROJECT_MEMBER_UPDATED event'); createEvent(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + const updated = _.omit(resource, 'resource'); + const original = _.omit(originalResource, 'resource'); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + if (project) { + if (updated.isPrimary && !original.isPrimary) { + createEvent(CONNECT_NOTIFICATION_EVENT.MEMBER_ASSIGNED_AS_OWNER, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + userId: updated.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** * PROJECT_ATTACHMENT_ADDED */ - app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED, ({ req, resource }) => { logger.debug('receive PROJECT_ATTACHMENT_ADDED event'); createEvent(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + const attachment = _.omit(resource, 'resource'); + + models.Project.findOne({ + 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); + + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** * PROJECT_ATTACHMENT_UPDATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED, ({ req, resource }) => { logger.debug('receive PROJECT_ATTACHMENT_UPDATED event'); createEvent(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** * PROJECT_ATTACHMENT_REMOVED */ - app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, ({ req, resource }) => { logger.debug('receive PROJECT_ATTACHMENT_REMOVED event'); createEvent(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + }).catch(err => null); // eslint-disable-line no-unused-vars }); + /** + * If the project is in draft status and the phase is in reviewed status, and it's the + * only phase in the project with that status, then send the plan ready event. + * + * @param {object} req the req + * @param {object} project the project + * @param {object} phase the phase that was created/updated + * @returns {Promise} void + */ + async function sendPlanReadyEventIfNeeded(req, project, phase) { + if (project.status === PROJECT_STATUS.DRAFT && + phase.status === PROJECT_PHASE_STATUS.REVIEWED) { + await models.ProjectPhase.count({ + where: { projectId: project.id, status: PROJECT_PHASE_STATUS.REVIEWED }, + }).then(((count) => { + // only send the plan ready event when this is the only reviewed phase in the project + if (count === 1) { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_READY, { + projectId: project.id, + phaseId: phase.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + })); + } + } + /** * PROJECT_PHASE_ADDED */ - app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, ({ req, resource }) => { logger.debug('receive PROJECT_PHASE_ADDED event'); createEvent(BUS_API_EVENT.PROJECT_PHASE_CREATED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + const created = _.omit(resource, 'resource'); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + allowedUsers: created.status === PROJECT_PHASE_STATUS.DRAFT ? + util.getTopcoderProjectMembers(project.members) : null, + }, logger); + return sendPlanReadyEventIfNeeded(req, project, created); + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** * PROJECT_PHASE_REMOVED */ - app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, ({ req, resource }) => { logger.debug('receive PROJECT_PHASE_REMOVED event'); createEvent(BUS_API_EVENT.PROJECT_PHASE_DELETED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + const deleted = _.omit(resource, 'resource'); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + allowedUsers: deleted.status === PROJECT_PHASE_STATUS.DRAFT ? + util.getTopcoderProjectMembers(project.members) : null, + }, logger); + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** * PROJECT_PHASE_UPDATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, ({ req, resource, originalResource, route, skipNotification }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_PHASE_UPDATED event'); createEvent(BUS_API_EVENT.PROJECT_PHASE_UPDATED, resource, logger); + + /* + Send event for Notification Service + */ + if (!skipNotification) { + const projectId = _.parseInt(req.params.projectId); + const phaseId = _.parseInt(req.params.phaseId); + const updated = _.omit(resource, 'resource'); + const original = _.omit(originalResource, 'resource'); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + logger.debug(`Fetched project ${projectId} for the phase ${phaseId}`); + const eventsMap = {}; + [ + ['duration', CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED], + ['startDate', CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED], + ['spentBudget', route === ROUTES.PHASES.UPDATE + ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_PAYMENT + : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_PAYMENT, + ], + ['progress', [route === ROUTES.PHASES.UPDATE + ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_PROGRESS + : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_PROGRESS, + CONNECT_NOTIFICATION_EVENT.PROJECT_PROGRESS_MODIFIED, + ]], + ['details', route === ROUTES.PHASES.UPDATE + ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_SCOPE + : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_SCOPE, + ], + ['status', route === ROUTES.PHASES.UPDATE + ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_TRANSITION_ACTIVE + : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_TRANSITION_ACTIVE, + PROJECT_PHASE_STATUS.ACTIVE, + ], + ['status', route === ROUTES.PHASES.UPDATE + ? CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED + : CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_TRANSITION_COMPLETED, + PROJECT_PHASE_STATUS.COMPLETED, + ], + // ideally we should validate the old value being 'DRAFT' but there is no other status from which + // we can move phase to REVIEWED status + ['status', CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, PROJECT_PHASE_STATUS.REVIEWED], + // ideally we should validate the old value being 'REVIEWED' but there is no other status from which + // we can move phase to DRAFT status + ['status', CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, PROJECT_PHASE_STATUS.DRAFT], + ].forEach(([key, events, sendIfNewEqual]) => { + // eslint-disable-next-line no-param-reassign + events = Array.isArray(events) ? events : [events]; + // eslint-disable-next-line no-param-reassign + events = _.filter(events, e => !eventsMap[e]); + + // send event(s) only if the target field's value was updated, or when an update matches a "sendIfNewEqual" value + if ((!sendIfNewEqual && !_.isEqual(original[key], updated[key])) || + (original[key] !== sendIfNewEqual && updated[key] === sendIfNewEqual)) { + events.forEach(event => createEvent(event, { + projectId, + phaseId, + projectUrl: connectProjectUrl(projectId), + originalPhase: original, + updatedPhase: updated, + projectName: project.name, + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + allowedUsers: updated.status === PROJECT_PHASE_STATUS.DRAFT ? + util.getTopcoderProjectMembers(project.members) : null, + }, logger)); + events.forEach((event) => { eventsMap[event] = true; }); + } + }); + + return sendPlanReadyEventIfNeeded(req, project, updated); + }).catch(err => null); // eslint-disable-line no-unused-vars + } }); + /** + * Send milestone notification if needed. + * @param {Object} req the request + * @param {Object} original the original milestone + * @param {Object} updated the updated milestone + * @param {Object} project the project + * @param {Object} timeline the updated timeline + * @returns {Promise} void + */ + function sendMilestoneNotification(req, original, updated, project, timeline) { + logger.debug('sendMilestoneNotification', original, updated); + // throw generic milestone updated bus api event + createEvent(CONNECT_NOTIFICATION_EVENT.MILESTONE_UPDATED, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + timeline, + originalMilestone: original, + updatedMilestone: updated, + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + // Send transition events + if (original.status !== updated.status) { + let event; + if (updated.status === MILESTONE_STATUS.COMPLETED) { + event = CONNECT_NOTIFICATION_EVENT.MILESTONE_TRANSITION_COMPLETED; + } else if (updated.status === MILESTONE_STATUS.ACTIVE) { + event = CONNECT_NOTIFICATION_EVENT.MILESTONE_TRANSITION_ACTIVE; + } else if (updated.status === MILESTONE_STATUS.PAUSED) { + event = CONNECT_NOTIFICATION_EVENT.MILESTONE_TRANSITION_PAUSED; + } + + if (event) { + createEvent(event, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + timeline, + originalMilestone: original, + updatedMilestone: updated, + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + } + + // Send notifications.connect.project.phase.milestone.waiting.customer event + const originalWaiting = _.get(original, 'details.metadata.waitingForCustomer', false); + const updatedWaiting = _.get(updated, 'details.metadata.waitingForCustomer', false); + if (!originalWaiting && updatedWaiting) { + createEvent(CONNECT_NOTIFICATION_EVENT.MILESTONE_WAITING_CUSTOMER, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + timeline, + originalMilestone: original, + updatedMilestone: updated, + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + } + /** * MILESTONE_ADDED. */ @@ -164,15 +667,95 @@ module.exports = (app, logger) => { logger.debug('receive MILESTONE_ADDED event'); createEvent(BUS_API_EVENT.MILESTONE_ADDED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + const created = _.omit(resource, 'resource'); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + if (project) { + createEvent(CONNECT_NOTIFICATION_EVENT.MILESTONE_ADDED, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + addedMilestone: created, + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + // sendMilestoneNotification(req, {}, created, project); + }) + .catch(err => null); // eslint-disable-line no-unused-vars }); /** * MILESTONE_UPDATED. */ - app.on(EVENT.ROUTING_KEY.MILESTONE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.MILESTONE_UPDATED, ({ + req, + resource, + originalResource, + cascadedUpdates, + skipNotification, + }) => { // eslint-disable-line no-unused-vars logger.debug(`receive MILESTONE_UPDATED event for milestone ${resource.id}`); createEvent(BUS_API_EVENT.MILESTONE_UPDATED, resource, logger); + + /* + Send event for Notification Service + */ + if (!skipNotification) { + const projectId = _.parseInt(req.params.projectId); + const timeline = _.omit(req.timeline.toJSON(), 'deletedAt', 'deletedBy'); + const updated = _.omit(resource, 'resource'); + const original = _.omit(originalResource, 'resource'); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + logger.debug(`Found project with id ${projectId}`); + return models.Milestone.getTimelineDuration(timeline.id) + .then(({ duration, progress }) => { + timeline.duration = duration; + timeline.progress = progress; + sendMilestoneNotification(req, original, updated, project, timeline); + + logger.debug('cascadedUpdates', cascadedUpdates); + if (cascadedUpdates && cascadedUpdates.milestones && cascadedUpdates.milestones.length > 0) { + _.each(cascadedUpdates.milestones, cascadedUpdate => + sendMilestoneNotification(req, cascadedUpdate.original, cascadedUpdate.updated, project, timeline), + ); + } + + // if timeline is modified + if (cascadedUpdates && cascadedUpdates.timeline) { + const cTimeline = cascadedUpdates.timeline; + // if endDate of the timeline is modified, raise TIMELINE_ADJUSTED event + if (!moment(cTimeline.original.endDate).isSame(cTimeline.updated.endDate)) { + // Raise Timeline changed event + createEvent(CONNECT_NOTIFICATION_EVENT.TIMELINE_ADJUSTED, { + projectId: project.id, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(project.id), + originalTimeline: cTimeline.original, + updatedTimeline: cTimeline.updated, + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + } + }); + }).catch(err => null); // eslint-disable-line no-unused-vars + } }); /** @@ -182,6 +765,29 @@ module.exports = (app, logger) => { logger.debug('receive MILESTONE_REMOVED event'); createEvent(BUS_API_EVENT.MILESTONE_REMOVED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + const deleted = _.omit(resource, 'resource'); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + if (project) { + createEvent(CONNECT_NOTIFICATION_EVENT.MILESTONE_REMOVED, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + removedMilestone: deleted, + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** @@ -232,10 +838,41 @@ module.exports = (app, logger) => { /** * TIMELINE_UPDATED */ - app.on(EVENT.ROUTING_KEY.TIMELINE_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.TIMELINE_UPDATED, ({ req, resource, originalResource }) => { // eslint-disable-line no-unused-vars logger.debug('receive TIMELINE_UPDATED event'); createEvent(BUS_API_EVENT.TIMELINE_UPDATED, resource, logger); + + /* + Send event for Notification Service + */ + const updated = _.omit(resource, 'resource'); + const original = _.omit(originalResource, 'resource'); + // send PROJECT_UPDATED Kafka message when one of the specified below properties changed + const watchProperties = ['startDate', 'endDate']; + if (!_.isEqual(_.pick(original, watchProperties), + _.pick(updated, watchProperties))) { + // req.params.projectId is set by validateTimelineIdParam middleware + const projectId = _.parseInt(req.params.projectId); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + if (project) { + createEvent(CONNECT_NOTIFICATION_EVENT.TIMELINE_ADJUSTED, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + originalTimeline: original, + updatedTimeline: updated, + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + }).catch(err => null); // eslint-disable-line no-unused-vars + } }); /** @@ -259,10 +896,55 @@ module.exports = (app, logger) => { /** * PROJECT_PHASE_PRODUCT_UPDATED */ - app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, ({ req, resource }) => { // eslint-disable-line no-unused-vars + app.on(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, ({ req, resource, originalResource, route }) => { // eslint-disable-line no-unused-vars logger.debug('receive PROJECT_PHASE_PRODUCT_UPDATED event'); createEvent(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + const updated = _.omit(resource, 'resource'); + const original = _.omit(originalResource, 'resource'); + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + // Spec changes + if (!_.isEqual(original.details, updated.details)) { + logger.debug(`Spec changed for product id ${updated.id}`); + + const busApiEvent = route === 'updatePhaseProducts' + ? CONNECT_NOTIFICATION_EVENT.PROJECT_PRODUCT_SPECIFICATION_MODIFIED + : CONNECT_NOTIFICATION_EVENT.PROJECT_WORKITEM_SPECIFICATION_MODIFIED; + + createEvent(busApiEvent, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + }, logger); + } + + const watchProperties = ['name', 'estimatedPrice', 'actualPrice', 'details']; + if (!_.isEqual(_.pick(original, watchProperties), + _.pick(updated, watchProperties))) { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, { + projectId, + projectName: project.name, + refCode: _.get(project, 'details.utm.code'), + projectUrl: connectProjectUrl(projectId), + userId: req.authUser.userId, + initiatorUserId: req.authUser.userId, + allowedUsers: updated.status === PROJECT_PHASE_STATUS.DRAFT ? + util.getTopcoderProjectMembers(project.members) : null, + }, logger); + } + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** @@ -272,6 +954,50 @@ module.exports = (app, logger) => { logger.debug('receive PROJECT_MEMBER_INVITE_CREATED event'); createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + const userId = resource.userId; + const email = resource.email; + const status = resource.status; + const role = resource.role; + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + logger.debug(util.isSSO); + if (status === INVITE_STATUS.REQUESTED) { + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_REQUESTED, { + projectId, + userId, + email, + role, + initiatorUserId: req.authUser.userId, + isSSO: util.isSSO(project), + }, logger); + } else { + // send event to bus api + logger.debug({ + projectId, + userId, + email, + role, + initiatorUserId: req.authUser.userId, + isSSO: util.isSSO(project), + }); + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_CREATED, { + projectId, + userId, + email, + role, + initiatorUserId: req.authUser.userId, + isSSO: util.isSSO(project), + }, logger); + } + }).catch(err => logger.error(err)); // eslint-disable-line no-unused-vars }); /** @@ -281,6 +1007,59 @@ module.exports = (app, logger) => { logger.debug('receive PROJECT_MEMBER_INVITE_UPDATED event'); createEvent(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, resource, logger); + + /* + Send event for Notification Service + */ + const projectId = _.parseInt(req.params.projectId); + const userId = resource.userId; + const email = resource.email; + const status = resource.status; + const role = resource.role; + const createdBy = resource.createdBy; + + models.Project.findOne({ + where: { id: projectId }, + }) + .then((project) => { + logger.debug(util.isSSO); + if (status === INVITE_STATUS.REQUEST_APPROVED) { + // send event to bus api + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_APPROVED, { + projectId, + userId, + originator: createdBy, + email, + role, + status, + initiatorUserId: req.authUser.userId, + isSSO: util.isSSO(project), + }, logger); + } else if (status === INVITE_STATUS.REQUEST_REJECTED) { + // send event to bus api + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_REJECTED, { + projectId, + userId, + originator: createdBy, + email, + role, + status, + initiatorUserId: req.authUser.userId, + isSSO: util.isSSO(project), + }, logger); + } else { + // send event to bus api + createEvent(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_UPDATED, { + projectId, + userId, + email, + role, + status, + initiatorUserId: req.authUser.userId, + isSSO: util.isSSO(project), + }, logger); + } + }).catch(err => null); // eslint-disable-line no-unused-vars }); /** diff --git a/src/events/index.js b/src/events/index.js index f5eee2e4..e161ef6b 100644 --- a/src/events/index.js +++ b/src/events/index.js @@ -1,5 +1,5 @@ -import { EVENT, BUS_API_EVENT } from '../constants'; +import { EVENT, CONNECT_NOTIFICATION_EVENT } from '../constants'; import { projectCreatedHandler, projectUpdatedHandler, projectDeletedHandler, projectUpdatedKafkaHandler } from './projects'; import { projectMemberAddedHandler, projectMemberRemovedHandler, @@ -57,18 +57,18 @@ export const rabbitHandlers = { export const kafkaHandlers = { // Events defined by project-api - [BUS_API_EVENT.PROJECT_UPDATED]: projectUpdatedKafkaHandler, - [BUS_API_EVENT.PROJECT_FILES_UPDATED]: projectUpdatedKafkaHandler, - [BUS_API_EVENT.PROJECT_TEAM_UPDATED]: projectUpdatedKafkaHandler, - [BUS_API_EVENT.PROJECT_PLAN_UPDATED]: projectUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED]: projectUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_UPDATED]: projectUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED]: projectUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED]: projectUpdatedKafkaHandler, // Events from message-service - [BUS_API_EVENT.TOPIC_CREATED]: projectUpdatedKafkaHandler, - [BUS_API_EVENT.TOPIC_UPDATED]: projectUpdatedKafkaHandler, - [BUS_API_EVENT.POST_CREATED]: projectUpdatedKafkaHandler, - [BUS_API_EVENT.POST_UPDATED]: projectUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.TOPIC_CREATED]: projectUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.TOPIC_UPDATED]: projectUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.POST_CREATED]: projectUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.POST_UPDATED]: projectUpdatedKafkaHandler, // Events coming from timeline/milestones (considering it as a separate module/service in future) - [BUS_API_EVENT.MILESTONE_TRANSITION_COMPLETED]: milestoneUpdatedKafkaHandler, - [BUS_API_EVENT.TIMELINE_ADJUSTED]: timelineAdjustedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.MILESTONE_TRANSITION_COMPLETED]: milestoneUpdatedKafkaHandler, + [CONNECT_NOTIFICATION_EVENT.TIMELINE_ADJUSTED]: timelineAdjustedKafkaHandler, }; diff --git a/src/events/milestones/index.js b/src/events/milestones/index.js index 83698255..8b717662 100644 --- a/src/events/milestones/index.js +++ b/src/events/milestones/index.js @@ -7,7 +7,7 @@ import Joi from 'joi'; import Promise from 'bluebird'; import util from '../../util'; // import { createEvent } from '../../services/busApi'; -import { EVENT, TIMELINE_REFERENCES, MILESTONE_STATUS, REGEX } from '../../constants'; +import { EVENT, TIMELINE_REFERENCES, MILESTONE_STATUS, REGEX, RESOURCES, ROUTES } from '../../constants'; import models from '../../models'; const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName'); @@ -219,14 +219,18 @@ async function milestoneUpdatedKafkaHandler(app, topic, payload) { }, ['progress', 'duration']); app.logger.debug(`Updated phase progress ${timeline.progress} and duration ${timeline.duration}`); app.logger.debug('Raising node event for PROJECT_PHASE_UPDATED'); - app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, { - req: { + util.sendResourceToKafkaBus( + { params: { projectId: project.id, phaseId: phase.id }, authUser: { userId: payload.userId }, }, - original: phase, - updated: _.omit(updatedPhase.toJSON(), 'deletedAt', 'deletedBy'), - }); + EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, + RESOURCES.PHASE, + _.omit(updatedPhase.toJSON(), 'deletedAt', 'deletedBy'), + phase, + _.get(project, 'details.settings.workstreams') ? ROUTES.WORKS.UPDATE : ROUTES.PHASES.UPDATE, + true, // don't send event to Notification Service as the main event here is updating milestones, not phase + ); } } } diff --git a/src/routes/attachments/create.spec.js b/src/routes/attachments/create.spec.js index 0b150f6c..e7e02940 100644 --- a/src/routes/attachments/create.spec.js +++ b/src/routes/attachments/create.spec.js @@ -7,7 +7,7 @@ import models from '../../models'; import util from '../../util'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); @@ -150,7 +150,7 @@ describe('Project Attachments', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED message when attachment added', (done) => { + it('sends send correct BUS API messages when attachment added', (done) => { request(server) .post(`/v5/projects/${project1.id}/attachments/`) .set({ @@ -164,17 +164,27 @@ describe('Project Attachments', () => { } else { // Wait for app message handler to complete testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, - sinon.match({ resource: RESOURCES.ATTACHMENT })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, - sinon.match({ title: body.title })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, - sinon.match({ description: body.description })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, - sinon.match({ category: body.category })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED, - sinon.match({ contentType: body.contentType })).should.be.true; + createEventSpy.calledThrice.should.be.true; + + 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, + })).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({ + projectId: project1.id, + projectName: project1.name, + projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, + userId: 40051333, + initiatorUserId: 40051333, + })).should.be.true; + done(); }); } diff --git a/src/routes/attachments/delete.js b/src/routes/attachments/delete.js index e63f095e..2e3a821a 100644 --- a/src/routes/attachments/delete.js +++ b/src/routes/attachments/delete.js @@ -56,7 +56,6 @@ module.exports = [ pattachment, { correlationId: req.id }, ); - // req.app.emit(EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, { req, pattachment }); // emit the event util.sendResourceToKafkaBus( req, diff --git a/src/routes/attachments/delete.spec.js b/src/routes/attachments/delete.spec.js index 59112545..d31d1718 100644 --- a/src/routes/attachments/delete.spec.js +++ b/src/routes/attachments/delete.spec.js @@ -9,7 +9,7 @@ import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); // eslint-disable-line no-unused-vars @@ -182,7 +182,7 @@ describe('Project Attachments delete', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED message when attachment deleted', (done) => { + it('sends send correct BUS API messages when attachment deleted', (done) => { request(server) .delete(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ @@ -195,11 +195,22 @@ describe('Project Attachments delete', () => { } else { // Wait for app message handler to complete testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, - sinon.match({ resource: RESOURCES.ATTACHMENT })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, - sinon.match({ id: attachment.id })).should.be.true; + createEventSpy.calledTwice.should.be.true; + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_REMOVED, sinon.match({ + resource: RESOURCES.ATTACHMENT, + id: attachment.id, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_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(); }); } diff --git a/src/routes/attachments/update.spec.js b/src/routes/attachments/update.spec.js index 4b7c774c..34957a42 100644 --- a/src/routes/attachments/update.spec.js +++ b/src/routes/attachments/update.spec.js @@ -7,7 +7,7 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); @@ -145,7 +145,7 @@ describe('Project Attachments update', () => { createEventSpy = sandbox.stub(busApi, 'createEvent'); }); - it('sends single BUS_API_EVENT.PROJECT_FILES_UPDATED message when attachment updated', (done) => { + it('sends send correct BUS API messages when attachment updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}/attachments/${attachment.id}`) .set({ @@ -159,13 +159,23 @@ describe('Project Attachments update', () => { } else { // Wait for app message handler to complete testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, - sinon.match({ resource: RESOURCES.ATTACHMENT })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, - sinon.match({ title: 'updated title' })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, - sinon.match({ description: 'updated description' })).should.be.true; + createEventSpy.calledTwice.should.be.true; + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_ATTACHMENT_UPDATED, sinon.match({ + resource: RESOURCES.ATTACHMENT, + title: 'updated title', + description: 'updated description', + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_FILES_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(); }); } diff --git a/src/routes/milestones/create.js b/src/routes/milestones/create.js index bf502dd8..0bbdf543 100644 --- a/src/routes/milestones/create.js +++ b/src/routes/milestones/create.js @@ -127,7 +127,14 @@ module.exports = [ req, EVENT.ROUTING_KEY.MILESTONE_UPDATED, RESOURCES.MILESTONE, - _.assign(_.pick(milestone.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt'))), + _.assign(_.pick(milestone.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt')), + // Pass the same object as original milestone even though, their time has changed. + // So far we don't use time properties in the handler so it's ok. But in general, we should pass + // the original milestones. <- TODO + _.assign(_.pick(milestone.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt')), + null, // no route + true, // don't send event to Notification Service as the main event here is updating one milestone + ), ); // Write to the response diff --git a/src/routes/milestones/create.spec.js b/src/routes/milestones/create.spec.js index 6c28436d..271e1d2f 100644 --- a/src/routes/milestones/create.spec.js +++ b/src/routes/milestones/create.spec.js @@ -10,7 +10,7 @@ import server from '../../app'; import testUtil from '../../tests/util'; import models from '../../models'; import busApi from '../../services/busApi'; -import { EVENT, RESOURCES, BUS_API_EVENT } from '../../constants'; +import { EVENT, RESOURCES, BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); @@ -542,7 +542,7 @@ describe('CREATE milestone', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.MILESTONE_ADDED when milestone created', (done) => { + it('sends send correct BUS API messages milestone created', (done) => { request(server) .post('/v5/timelines/1/milestones') .set({ @@ -556,13 +556,35 @@ describe('CREATE milestone', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.callCount.should.be.eql(3); - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_ADDED, - sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_ADDED, - sinon.match({ name: 'milestone 4' })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_ADDED, - sinon.match({ description: 'description 4' })).should.be.true; + createEventSpy.callCount.should.be.eql(4); + + // added a new milestone + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_ADDED, sinon.match({ + resource: RESOURCES.MILESTONE, + name: 'milestone 4', + description: 'description 4', + order: 2, + })).should.be.true; + + // as order of the next milestones after the added one have been updated, we send events about their update + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ + resource: RESOURCES.MILESTONE, + order: 3, + })).should.be.true; + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ + resource: RESOURCES.MILESTONE, + order: 4, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_ADDED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } diff --git a/src/routes/milestones/delete.spec.js b/src/routes/milestones/delete.spec.js index d1f4ada2..ca3da3ba 100644 --- a/src/routes/milestones/delete.spec.js +++ b/src/routes/milestones/delete.spec.js @@ -9,7 +9,7 @@ import chai from 'chai'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; -import { EVENT, RESOURCES, BUS_API_EVENT } from '../../constants'; +import { EVENT, RESOURCES, BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT } from '../../constants'; import busApi from '../../services/busApi'; const should = chai.should(); // eslint-disable-line no-unused-vars @@ -379,7 +379,7 @@ describe('DELETE milestone', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.MILESTONE_REMOVED when milestone removed', (done) => { + it('sends send correct BUS API messages when milestone removed', (done) => { request(server) .delete('/v5/timelines/1/milestones/1') .set({ @@ -391,11 +391,22 @@ describe('DELETE milestone', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_REMOVED, - sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_REMOVED, - sinon.match({ id: 1 })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_REMOVED, sinon.match({ + resource: RESOURCES.MILESTONE, + id: 1, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_REMOVED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } diff --git a/src/routes/milestones/update.js b/src/routes/milestones/update.js index bb7d48cd..850d531f 100644 --- a/src/routes/milestones/update.js +++ b/src/routes/milestones/update.js @@ -334,12 +334,13 @@ module.exports = [ ); // emit the event - util.sendResourceToKafkaBus( + // we cannot use `util.sendResourceToKafkaBus` as we have to pass a custom param `cascadedUpdates` + req.app.emit(EVENT.ROUTING_KEY.MILESTONE_UPDATED, { req, - EVENT.ROUTING_KEY.MILESTONE_UPDATED, - RESOURCES.MILESTONE, - _.assign(entityToUpdate, _.pick(updated, 'id', 'updatedAt')), - ); + resource: _.assign({ resource: RESOURCES.MILESTONE }, updated), + originalResource: _.assign({ resource: RESOURCES.MILESTONE }, original), + cascadedUpdates, + }); // Write to response res.json(updated); diff --git a/src/routes/milestones/update.spec.js b/src/routes/milestones/update.spec.js index c41c0431..bf8b1c7e 100644 --- a/src/routes/milestones/update.spec.js +++ b/src/routes/milestones/update.spec.js @@ -11,7 +11,7 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { EVENT, RESOURCES, MILESTONE_STATUS, BUS_API_EVENT } from '../../constants'; +import { EVENT, RESOURCES, MILESTONE_STATUS, BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); @@ -1298,14 +1298,13 @@ describe('UPDATE Milestone', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.MILESTONE_UPDATED when milestone duration updated', (done) => { + it('sends send correct BUS API messages when milestone details updated and waiting for customer', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ Authorization: `Bearer ${testUtil.jwts.copilot}`, }) .send({ - // duration: 1, details: { metadata: { waitingForCustomer: true }, }, @@ -1316,18 +1315,26 @@ describe('UPDATE Milestone', () => { done(err); } else { testUtil.wait(() => { - // 5 milestones in total, so it would trigger 5 events - // 4 MILESTONE_UPDATED events are for 4 non deleted milestones - // 1 TIMELINE_ADJUSTED event, because timeline's end date updated - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, - sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, - sinon.match({ - details: { - metadata: { waitingForCustomer: true }, - }, - })).should.be.true; + createEventSpy.callCount.should.be.eql(3); + + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ + resource: RESOURCES.MILESTONE, + details: { + metadata: { waitingForCustomer: true }, + }, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_WAITING_CUSTOMER) + .should.be.true; + done(); }); } @@ -1389,7 +1396,7 @@ describe('UPDATE Milestone', () => { }); }); - it('should ONLY send message BUS_API_EVENT.MILESTONE_UPDATED when milestone order updated', (done) => { + it('should send correct BUS API messages when milestone order updated', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -1404,18 +1411,29 @@ describe('UPDATE Milestone', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, - sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, - sinon.match({ order: 2 })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ + resource: RESOURCES.MILESTONE, + order: 2, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } }); }); - it('should ONLY send message BUS_API_EVENT.MILESTONE_UPDATED when milestone plannedText updated', (done) => { + it('should send correct BUS API messages when milestone plannedText updated', (done) => { request(server) .patch('/v5/timelines/1/milestones/1') .set({ @@ -1430,11 +1448,22 @@ describe('UPDATE Milestone', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, - sinon.match({ resource: RESOURCES.MILESTONE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, - sinon.match({ plannedText: 'new text' })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.MILESTONE_UPDATED, sinon.match({ + resource: RESOURCES.MILESTONE, + plannedText: 'new text', + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MILESTONE_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } diff --git a/src/routes/phaseProducts/create.spec.js b/src/routes/phaseProducts/create.spec.js index d9ebba39..e987ed42 100644 --- a/src/routes/phaseProducts/create.spec.js +++ b/src/routes/phaseProducts/create.spec.js @@ -7,6 +7,7 @@ import server from '../../app'; import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; +import { RESOURCES, BUS_API_EVENT } from '../../constants'; const should = chai.should(); @@ -299,7 +300,7 @@ describe('Phase Products', () => { sandbox.restore(); }); - it('should not send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_ADDED when product phase created', (done) => { + it('should send correct BUS API messages when product phase created', (done) => { request(server) .post(`/v5/projects/${projectId}/phases/${phaseId}/products`) .set({ @@ -313,7 +314,12 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + })).should.be.true; + done(); }); } diff --git a/src/routes/phaseProducts/delete.spec.js b/src/routes/phaseProducts/delete.spec.js index 174c03c1..c6e7c358 100644 --- a/src/routes/phaseProducts/delete.spec.js +++ b/src/routes/phaseProducts/delete.spec.js @@ -7,6 +7,7 @@ import server from '../../app'; import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; +import { BUS_API_EVENT, RESOURCES } from '../../constants'; const should = chai.should(); // eslint-disable-line no-unused-vars @@ -259,7 +260,7 @@ describe('Phase Products', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_REMOVED when product phase removed', (done) => { + it('should send correct BUS API messages when product phase removed', (done) => { request(server) .delete(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ @@ -271,7 +272,12 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + })).should.be.true; + done(); }); } diff --git a/src/routes/phaseProducts/update.js b/src/routes/phaseProducts/update.js index c1668371..7ae577da 100644 --- a/src/routes/phaseProducts/update.js +++ b/src/routes/phaseProducts/update.js @@ -5,7 +5,7 @@ import Joi from 'joi'; 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, ROUTES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -78,7 +78,9 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, RESOURCES.PHASE_PRODUCT, - _.assign(updatedProps, _.pick(updated, 'id', 'updatedAt'))); + updatedValue, + previousValue, + ROUTES.PHASE_PRODUCTS.UPDATE); res.json(updated); }).catch(err => next(err)); diff --git a/src/routes/phaseProducts/update.spec.js b/src/routes/phaseProducts/update.spec.js index b23a5672..be892212 100644 --- a/src/routes/phaseProducts/update.spec.js +++ b/src/routes/phaseProducts/update.spec.js @@ -7,7 +7,7 @@ import server from '../../app'; import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); @@ -291,7 +291,7 @@ describe('Phase Products', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when name updated', (done) => { + it('should send correct BUS API messages when name updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ @@ -307,18 +307,29 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ name: 'new name' })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + name: 'new name', + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when estimatedPrice updated', (done) => { + it('should send correct BUS API messages when estimatedPrice updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ @@ -334,18 +345,29 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ estimatedPrice: 123 })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + estimatedPrice: 123, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when actualPrice updated', (done) => { + it('should send correct BUS API messages when actualPrice updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ @@ -361,18 +383,29 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ actualPrice: 123 })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + actualPrice: 123, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when details updated', (done) => { + it('should send correct BUS API messages when details updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ @@ -388,18 +421,31 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ details: 'something' })).should.be.true; + createEventSpy.callCount.should.be.eql(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + details: 'something', + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PRODUCT_SPECIFICATION_MODIFIED) + .should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } }); }); - it('should not send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when type updated', (done) => { + it('should send correct BUS API messages when type updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}/products/${productId}`) .set({ @@ -415,11 +461,13 @@ describe('Phase Products', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ resource: RESOURCES.PHASE_PRODUCT })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ type: 'another type' })).should.be.true; + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + type: 'another type', + })).should.be.true; + done(); }); } diff --git a/src/routes/phases/create.js b/src/routes/phases/create.js index 3d1666a6..52071634 100644 --- a/src/routes/phases/create.js +++ b/src/routes/phases/create.js @@ -158,7 +158,12 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, RESOURCES.PHASE, + _.assign(_.pick(phase.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt')), + // Pass the same object as original phase even though, the order has changed. + // So far we don't use the order so it's ok. But in general, we should pass + // the original phases. <- TODO _.assign(_.pick(phase.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt'))), + true, // don't send event to Notification Service as the main event here is updating one phase ); res.status(201).json(newProjectPhase); diff --git a/src/routes/phases/create.spec.js b/src/routes/phases/create.spec.js index f8c9596a..94ee55c1 100644 --- a/src/routes/phases/create.spec.js +++ b/src/routes/phases/create.spec.js @@ -12,7 +12,7 @@ import messageService from '../../services/messageService'; import RabbitMQService from '../../services/rabbitmq'; import mockRabbitMQ from '../../tests/mockRabbitMQ'; import { - BUS_API_EVENT, RESOURCES, + BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT, } from '../../constants'; const should = chai.should(); @@ -49,6 +49,7 @@ const validatePhase = (resJson, expectedPhase) => { describe('Project Phases', () => { let projectId; + let projectName; const memberUser = { handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, @@ -81,6 +82,7 @@ describe('Project Phases', () => { testUtil.clearDb() .then(() => models.Project.create(project).then((p) => { projectId = p.id; + projectName = p.name; // create members return models.ProjectMember.bulkCreate([{ id: 1, @@ -434,7 +436,7 @@ describe('Project Phases', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_ADDED when phase added', (done) => { + it('should send correct BUS API messages when phase added', (done) => { request(server) .post(`/v5/projects/${projectId}/phases/`) .set({ @@ -448,19 +450,26 @@ describe('Project Phases', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, - sinon.match({ resource: RESOURCES.PHASE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, - sinon.match({ name: body.name })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, - sinon.match({ status: body.status })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, - sinon.match({ budget: body.budget })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, - sinon.match({ progress: body.progress })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, - sinon.match({ projectId })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, sinon.match({ + resource: RESOURCES.PHASE, + name: body.name, + status: body.status, + budget: body.budget, + progress: body.progress, + projectId, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId, + projectName, + projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } diff --git a/src/routes/phases/delete.spec.js b/src/routes/phases/delete.spec.js index 99a295f9..17eea968 100644 --- a/src/routes/phases/delete.spec.js +++ b/src/routes/phases/delete.spec.js @@ -14,6 +14,7 @@ import mockRabbitMQ from '../../tests/mockRabbitMQ'; import { BUS_API_EVENT, RESOURCES, + CONNECT_NOTIFICATION_EVENT, } from '../../constants'; const should = chai.should(); // eslint-disable-line no-unused-vars @@ -58,6 +59,7 @@ const body = { describe('Project Phases', () => { let projectId; let phaseId; + let projectName; const memberUser = { handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, @@ -99,6 +101,7 @@ describe('Project Phases', () => { .then(() => { models.Project.create(project).then((p) => { projectId = p.id; + projectName = p.name; // create members models.ProjectMember.bulkCreate([{ id: 1, @@ -253,7 +256,7 @@ describe('Project Phases', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_DELETED when phase removed', (done) => { + it('should send correct BUS API messages when phase removed', (done) => { request(server) .delete(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ @@ -265,11 +268,22 @@ describe('Project Phases', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, - sinon.match({ resource: RESOURCES.PHASE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, - sinon.match({ id: phaseId })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId, + projectName, + projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } diff --git a/src/routes/phases/update.js b/src/routes/phases/update.js index 7c82ac15..7bafa010 100644 --- a/src/routes/phases/update.js +++ b/src/routes/phases/update.js @@ -6,7 +6,7 @@ import Sequelize from 'sequelize'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants'; +import { EVENT, RESOURCES, TIMELINE_REFERENCES, ROUTES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -151,10 +151,12 @@ module.exports = [ .then((allPhases) => { req.log.debug('updated project phase', JSON.stringify(updated, null, 2)); + const updatedValue = updated.get({ plain: true }); + // emit original and updated project phase information req.app.services.pubsub.publish( EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, - { original: previousValue, updated, allPhases, route: TIMELINE_REFERENCES.PHASE }, + { original: previousValue, updated: updatedValue, allPhases, route: TIMELINE_REFERENCES.PHASE }, { correlationId: req.id }, ); @@ -163,7 +165,9 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, RESOURCES.PHASE, - _.assign(updatedProps, _.pick(updated, 'id', 'updatedAt'))); + updatedValue, + previousValue, + ROUTES.PHASES.UPDATE); res.json(updated); }) diff --git a/src/routes/phases/update.spec.js b/src/routes/phases/update.spec.js index 77630b8e..408670ad 100644 --- a/src/routes/phases/update.spec.js +++ b/src/routes/phases/update.spec.js @@ -14,6 +14,7 @@ import mockRabbitMQ from '../../tests/mockRabbitMQ'; import { BUS_API_EVENT, RESOURCES, + CONNECT_NOTIFICATION_EVENT, } from '../../constants'; const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); @@ -64,8 +65,10 @@ const validatePhase = (resJson, expectedPhase) => { describe('Project Phases', () => { let projectId; + let projectName; let phaseId; let phaseId2; + let phaseId3; const memberUser = { handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, userId: testUtil.getDecodedToken(testUtil.jwts.member).userId, @@ -107,6 +110,7 @@ describe('Project Phases', () => { .then(() => { models.Project.create(project).then((p) => { projectId = p.id; + projectName = p.name; // create members models.ProjectMember.bulkCreate([{ id: 1, @@ -135,6 +139,7 @@ describe('Project Phases', () => { .then((createdPhases) => { phaseId = createdPhases[0].id; phaseId2 = createdPhases[1].id; + phaseId3 = createdPhases[2].id; done(); }); @@ -372,8 +377,204 @@ describe('Project Phases', () => { sandbox.restore(); }); + it('should send correct BUS API messages when spentBudget updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + spentBudget: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId, + updatedBy: testUtil.userIds.copilot, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_PAYMENT).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when progress updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + progress: 50, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId, + updatedBy: testUtil.userIds.copilot, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_PROGRESS).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PROGRESS_MODIFIED).should.be.true; + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when details updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + details: { + text: 'something', + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId, + updatedBy: testUtil.userIds.copilot, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_UPDATE_SCOPE).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when status updated (completed)', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + status: 'completed', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId, + updatedBy: testUtil.userIds.copilot, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_TRANSITION_COMPLETED).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when status updated (active)', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId3}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + status: 'active', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId3, + updatedBy: testUtil.userIds.copilot, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PHASE_TRANSITION_ACTIVE).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when budget updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + budget: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId, + updatedBy: testUtil.userIds.copilot, + })).should.be.true; - it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when startDate updated', (done) => { + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when startDate updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ @@ -389,13 +590,23 @@ describe('Project Phases', () => { done(err); } else { testUtil.wait(() => { - // createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ resource: RESOURCES.PHASE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ id: phaseId })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ updatedBy: testUtil.userIds.copilot })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId, + updatedBy: testUtil.userIds.copilot, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId, + projectName, + projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } @@ -403,7 +614,7 @@ describe('Project Phases', () => { }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when duration updated', (done) => { + it('should send correct BUS API messages when duration updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/phases/${phaseId}`) .set({ @@ -419,9 +630,83 @@ describe('Project Phases', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ duration: 100 })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + duration: 100, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId, + projectName, + projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when order updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + order: 100, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId, + updatedBy: testUtil.userIds.copilot, + })).should.be.true; + + // NOTE: no other event should be called, as this phase doesn't move any other phases + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when endDate updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/phases/${phaseId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .send({ + endDate: new Date(), + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: phaseId, + updatedBy: testUtil.userIds.copilot, + })).should.be.true; + done(); }); } diff --git a/src/routes/projectMemberInvites/create.js b/src/routes/projectMemberInvites/create.js index 9d844ffa..e4620444 100644 --- a/src/routes/projectMemberInvites/create.js +++ b/src/routes/projectMemberInvites/create.js @@ -8,7 +8,9 @@ import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; import { PROJECT_MEMBER_ROLE, PROJECT_MEMBER_MANAGER_ROLES, - MANAGER_ROLES, INVITE_STATUS, EVENT, RESOURCES, USER_ROLE, MAX_PARALLEL_REQUEST_QTY } from '../../constants'; + MANAGER_ROLES, INVITE_STATUS, EVENT, RESOURCES, USER_ROLE, + MAX_PARALLEL_REQUEST_QTY, CONNECT_NOTIFICATION_EVENT } from '../../constants'; +import { createEvent } from '../../services/busApi'; /** * API to create member invite to project. @@ -171,8 +173,9 @@ const buildCreateInvitePromises = (req, invite, invites, data, failed, members) return invitePromises; }; -const sendInviteEmail = (req, projectId) => { +const sendInviteEmail = (req, projectId, invite) => { req.log.debug(req.authUser); + const emailEventType = CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED; const promises = [ models.Project.findOne({ where: { id: projectId }, @@ -182,6 +185,40 @@ const sendInviteEmail = (req, projectId) => { ]; return Promise.all(promises).then((responses) => { req.log.debug(responses); + const project = responses[0]; + const initiator = responses[1] && responses[1].length ? responses[1][0] : { + userId: req.authUser.userId, + firstName: 'Connect', + lastName: 'User', + }; + createEvent(emailEventType, { + data: { + connectURL: config.get('connectUrl'), + accountsAppURL: config.get('accountsAppUrl'), + subject: config.get('inviteEmailSubject'), + projects: [{ + name: project.name, + projectId, + sections: [ + { + EMAIL_INVITES: true, + title: config.get('inviteEmailSectionTitle'), + projectName: project.name, + projectId, + initiator, + isSSO: util.isSSO(project), + }, + ], + }], + }, + recipients: [invite.email], + version: 'v3', + from: { + name: config.get('EMAIL_INVITE_FROM_NAME'), + email: config.get('EMAIL_INVITE_FROM_EMAIL'), + }, + categories: [`${process.env.NODE_ENV}:${emailEventType}`.toLowerCase()], + }, req.log); }).catch((error) => { req.log.error(error); }); @@ -296,7 +333,7 @@ module.exports = [ ); // send email invite (async) if (v.email && !v.userId && v.status === INVITE_STATUS.PENDING) { - sendInviteEmail(req, projectId); + sendInviteEmail(req, projectId, v); } }); return values; diff --git a/src/routes/projectMemberInvites/create.spec.js b/src/routes/projectMemberInvites/create.spec.js index ea9ceaf0..6aac5c00 100644 --- a/src/routes/projectMemberInvites/create.spec.js +++ b/src/routes/projectMemberInvites/create.spec.js @@ -15,6 +15,7 @@ import { INVITE_STATUS, BUS_API_EVENT, RESOURCES, + CONNECT_NOTIFICATION_EVENT, } from '../../constants'; const should = chai.should(); @@ -871,7 +872,7 @@ describe('Project Member Invite create', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED message when userId invite added', (done) => { + it('should send correct BUS API messages when invite added by userId', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, @@ -896,23 +897,30 @@ describe('Project Member Invite create', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, - sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, - sinon.match({ projectId: project1.id })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, - sinon.match({ userId: 3 })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, - sinon.match({ email: null })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER_INVITE, + projectId: project1.id, + userId: 3, + 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, + email: null, + isSSO: false, + })).should.be.true; + done(); }); } }); }); - it('sends BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED message when email invite added', (done) => { + it('should send correct BUS API messages when invite added by email', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, @@ -937,16 +945,26 @@ describe('Project Member Invite create', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, - sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, - sinon.match({ projectId: project1.id })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, - sinon.match({ userId: null })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, - sinon.match({ email: 'hello@world.com' })).should.be.true; + createEventSpy.callCount.should.be.eql(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER_INVITE, + projectId: project1.id, + userId: null, + email: 'hello@world.com', + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ + projectId: project1.id, + userId: null, + email: 'hello@world.com', + isSSO: false, + })).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_EMAIL_INVITE_CREATED, sinon.match({ + recipients: ['hello@world.com'], + })).should.be.true; + done(); }); } diff --git a/src/routes/projectMemberInvites/update.spec.js b/src/routes/projectMemberInvites/update.spec.js index 81de61f6..49e53e3c 100644 --- a/src/routes/projectMemberInvites/update.spec.js +++ b/src/routes/projectMemberInvites/update.spec.js @@ -8,7 +8,14 @@ import server from '../../app'; import util from '../../util'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES, USER_ROLE, PROJECT_MEMBER_ROLE, INVITE_STATUS } from '../../constants'; +import { + BUS_API_EVENT, + RESOURCES, + USER_ROLE, + PROJECT_MEMBER_ROLE, + INVITE_STATUS, + CONNECT_NOTIFICATION_EVENT, +} from '../../constants'; const should = chai.should(); @@ -301,8 +308,7 @@ describe('Project member invite update', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('Accept invite sends BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED ' + - 'and BUS_API_EVENT.PROJECT_MEMBER_ADDED messages', (done) => { + it('should send correct BUS API messages when invite is accepted', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, @@ -326,18 +332,51 @@ describe('Project member invite update', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, - sinon.match({ resource: RESOURCES.PROJECT_MEMBER_INVITE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, - sinon.match({ projectId: project1.id })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, - sinon.match({ userId: invite1.userId })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, - sinon.match({ status: INVITE_STATUS.ACCEPTED })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, - sinon.match({ email: null })).should.be.true; + createEventSpy.callCount.should.be.eql(5); + + /* + 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, + status: INVITE_STATUS.ACCEPTED, + email: null, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({ + projectId: project1.id, + userId: invite1.userId, + status: INVITE_STATUS.ACCEPTED, + email: null, + isSSO: false, + })).should.be.true; + + /* + 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, + })).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, + })).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ + projectId: project1.id, + projectName: project1.name, + userId: invite1.userId, + initiatorUserId: 40051331, + })).should.be.true; + done(); }); } diff --git a/src/routes/projectMembers/create.js b/src/routes/projectMembers/create.js index a6cc9acd..cb72ebc7 100644 --- a/src/routes/projectMembers/create.js +++ b/src/routes/projectMembers/create.js @@ -3,7 +3,7 @@ import Joi from 'joi'; import validate from 'express-validation'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import util from '../../util'; -import { INVITE_STATUS, MANAGER_ROLES, PROJECT_MEMBER_ROLE, USER_ROLE, EVENT, RESOURCES } from '../../constants'; +import { INVITE_STATUS, MANAGER_ROLES, PROJECT_MEMBER_ROLE, USER_ROLE } from '../../constants'; import models from '../../models'; /** @@ -145,37 +145,19 @@ module.exports = [ return next(err); } - return util.addUserToProject(req, member) - .then((newMember) => { - let invite; - return models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(projectId, null, newMember.userId) - .then((_invite) => { - invite = _invite; + return util.addUserToProject(req, member) // Kafka event is emitted inside `addUserToProject` + .then(newMember => + models.ProjectMemberInvite.getPendingInviteByEmailOrUserId(projectId, null, newMember.userId) + .then((invite) => { if (!invite) { - // emit the event - util.sendResourceToKafkaBus( - req, - EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, - RESOURCES.PROJECT_MEMBER, - newMember); - - return res.status(201) - .json(newMember); + return res.status(201).json(newMember); } return invite.update({ status: INVITE_STATUS.ACCEPTED, }) - .then(() => { - // emit the event - util.sendResourceToKafkaBus( - req, - EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, - RESOURCES.PROJECT_MEMBER, - newMember); - return res.status(201).json(newMember); - }); - }); - }); + .then(() => res.status(201).json(newMember)); + }), + ); }) .catch(err => next(err)); }, diff --git a/src/routes/projectMembers/create.spec.js b/src/routes/projectMembers/create.spec.js index 21e05f60..7e193990 100644 --- a/src/routes/projectMembers/create.spec.js +++ b/src/routes/projectMembers/create.spec.js @@ -8,7 +8,8 @@ import models from '../../models'; import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; -import { USER_ROLE } from '../../constants'; +import busApi from '../../services/busApi'; +import { USER_ROLE, BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT, INVITE_STATUS } from '../../constants'; const should = chai.should(); @@ -199,5 +200,164 @@ describe('Project Members create', () => { } }); }); + + 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 a manager added', (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.MANAGER, + }], + }, + }, + }), + post: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: {}, + }, + }, + }), + }); + sandbox.stub(util, 'getHttpClient', () => mockHttpClient); + request(server) + .post(`/v5/projects/${project1.id}/members/`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(201) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_ADDED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER, + projectId: project1.id, + userId: 40051334, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED_MANAGER).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ + projectId: project1.id, + projectName: project1.name, + projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, + userId: 40051334, + initiatorUserId: 40051334, + })).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when copilot added', (done) => { + request(server) + .post(`/v5/projects/${project1.id}/members/invite`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + userIds: [40051332], + role: 'copilot', + }) + .expect(201) + .end((err) => { + if (err) { + done(err); + } else { + request(server) + .put(`/v5/projects/${project1.id}/members/invite`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .send({ + userId: 40051332, + status: 'accepted', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err2) => { + if (err2) { + done(err2); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.equal(7); + + /* + Copilot invitation requested + */ + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_CREATED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER_INVITE, + projectId: project1.id, + userId: 40051332, + email: null, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_REQUESTED).should.be.true; + + /* + Copilot invitation accepted + */ + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_INVITE_UPDATED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER_INVITE, + projectId: project1.id, + userId: 40051332, + status: INVITE_STATUS.ACCEPTED, + email: null, + })).should.be.true; + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_ADDED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER, + projectId: project1.id, + userId: 40051332, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_MEMBER_INVITE_UPDATED).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_JOINED_COPILOT).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ + projectId: project1.id, + projectName: project1.name, + projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, + userId: 40051336, + initiatorUserId: 40051336, + })).should.be.true; + done(); + }); + } + }); + } + }); + }); + }); }); }); diff --git a/src/routes/projectMembers/delete.js b/src/routes/projectMembers/delete.js index 1a145488..d13a8f71 100644 --- a/src/routes/projectMembers/delete.js +++ b/src/routes/projectMembers/delete.js @@ -83,7 +83,7 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, RESOURCES.PROJECT_MEMBER, - { id: pmember.id }); + pmember); res.status(204).json({}); }).catch(err => next(err)); }, diff --git a/src/routes/projectMembers/delete.spec.js b/src/routes/projectMembers/delete.spec.js index 5eddc353..dd1556ae 100644 --- a/src/routes/projectMembers/delete.spec.js +++ b/src/routes/projectMembers/delete.spec.js @@ -9,7 +9,7 @@ import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); @@ -328,7 +328,7 @@ describe('Project members delete', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends BUS_API_EVENT.PROJECT_MEMBER_REMOVED message when manager removed', (done) => { + it('should send correct BUS API messages when manager left', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { post: () => Promise.resolve({ status: 200, @@ -355,19 +355,30 @@ describe('Project members delete', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, - sinon.match({ resource: RESOURCES.PROJECT_MEMBER })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, - sinon.match({ id: member2.id })).should.be.true; + createEventSpy.callCount.should.equal(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER, + id: member2.id, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_LEFT).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ + projectId: project1.id, + projectName: project1.name, + projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, + userId: 40051334, + initiatorUserId: 40051334, + })).should.be.true; + done(); }); } }); }); - it('sends BUS_API_EVENT.PROJECT_MEMBER_REMOVED message when copilot removed', (done) => { + it('should send correct BUS API messages when copilot removed', (done) => { request(server) .delete(`/v5/projects/${project1.id}/members/${member1.id}`) .set({ @@ -379,12 +390,23 @@ describe('Project members delete', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, - sinon.match({ resource: RESOURCES.PROJECT_MEMBER })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, - sinon.match({ id: member1.id })).should.be.true; + createEventSpy.callCount.should.equal(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_REMOVED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER, + id: member1.id, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.MEMBER_REMOVED).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ + projectId: project1.id, + projectName: project1.name, + projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, + userId: 40051334, + initiatorUserId: 40051334, + })).should.be.true; + done(); }); } diff --git a/src/routes/projectMembers/update.js b/src/routes/projectMembers/update.js index 48ee069a..48d976bc 100644 --- a/src/routes/projectMembers/update.js +++ b/src/routes/projectMembers/update.js @@ -113,7 +113,8 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, RESOURCES.PROJECT_MEMBER, - projectMember); + projectMember, + previousValue); req.log.debug('updated project member', projectMember); res.json(projectMember); }) diff --git a/src/routes/projectMembers/update.spec.js b/src/routes/projectMembers/update.spec.js index 38594b6c..28c3d666 100644 --- a/src/routes/projectMembers/update.spec.js +++ b/src/routes/projectMembers/update.spec.js @@ -8,7 +8,7 @@ import server from '../../app'; import util from '../../util'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT, RESOURCES } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); @@ -477,7 +477,7 @@ describe('Project members update', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends single BUS_API_EVENT.PROJECT_MEMBER_UPDATED message when user role updated', (done) => { + it('should send correct BUS API messages when user role updated', (done) => { const mockHttpClient = _.merge(testUtil.mockHttpClient, { get: () => Promise.resolve({ status: 200, @@ -508,16 +508,24 @@ describe('Project members update', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, - sinon.match({ resource: RESOURCES.PROJECT_MEMBER })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, - sinon.match({ id: member2.id })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, - sinon.match({ role: 'customer' })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, - sinon.match({ userId: 40051332 })).should.be.true; + createEventSpy.callCount.should.equal(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_MEMBER_UPDATED, sinon.match({ + resource: RESOURCES.PROJECT_MEMBER, + id: member2.id, + role: 'customer', + userId: 40051332, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_TEAM_UPDATED, sinon.match({ + projectId: project1.id, + projectName: project1.name, + projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } diff --git a/src/routes/projects/update.spec.js b/src/routes/projects/update.spec.js index 78ebe966..ea49063a 100644 --- a/src/routes/projects/update.spec.js +++ b/src/routes/projects/update.spec.js @@ -12,6 +12,7 @@ import busApi from '../../services/busApi'; import { PROJECT_STATUS, BUS_API_EVENT, + CONNECT_NOTIFICATION_EVENT, } from '../../constants'; const should = chai.should(); @@ -745,7 +746,7 @@ describe('Project', () => { createEventSpy = sandbox.spy(busApi, 'createEvent'); }); - it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project status update', (done) => { + it('should send correct BUS API messages when project status updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}`) .set({ @@ -753,7 +754,6 @@ describe('Project', () => { }) .send({ status: PROJECT_STATUS.COMPLETED, - }) .expect(200) .end((err) => { @@ -761,14 +761,32 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; + createEventSpy.callCount.should.equal(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({ + resource: 'project', + id: project1.id, + status: PROJECT_STATUS.COMPLETED, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_COMPLETED).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_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 single BUS_API_EVENT.PROJECT_UPDATED message on project details update', (done) => { + it('should send correct BUS API messages when project details updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}`) .set({ @@ -785,22 +803,32 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ resource: 'project' })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ id: project1.id })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ details: { info: 'something' } })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ updatedBy: testUtil.userIds.admin })).should.be.true; + createEventSpy.callCount.should.equal(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({ + resource: 'project', + id: project1.id, + details: { info: 'something' }, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_SPECIFICATION_MODIFIED).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_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 single BUS_API_EVENT.PROJECT_UPDATED message on project name update', (done) => { + it('should send correct BUS API messages when project name updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}`) .set({ @@ -808,7 +836,6 @@ describe('Project', () => { }) .send({ name: 'New project name', - }) .expect(200) .end((err) => { @@ -816,22 +843,32 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ resource: 'project' })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ id: project1.id })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ name: 'New project name' })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ updatedBy: testUtil.userIds.admin })).should.be.true; + createEventSpy.callCount.should.equal(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({ + resource: 'project', + id: project1.id, + name: 'New project name', + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_SPECIFICATION_MODIFIED).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_UPDATED, sinon.match({ + projectId: project1.id, + projectName: 'New project name', + projectUrl: `https://local.topcoder-dev.com/projects/${project1.id}`, + userId: 40051333, + initiatorUserId: 40051333, + })).should.be.true; + done(); }); } }); }); - it('sends single BUS_API_EVENT.PROJECT_UPDATED message on project description update', (done) => { + it('should send correct BUS API messages when project description updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}`) .set({ @@ -839,7 +876,6 @@ describe('Project', () => { }) .send({ description: 'Updated description', - }) .expect(200) .end((err) => { @@ -847,22 +883,32 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ resource: 'project' })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ id: project1.id })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ description: 'Updated description' })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ updatedBy: testUtil.userIds.admin })).should.be.true; + createEventSpy.callCount.should.equal(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({ + resource: 'project', + id: project1.id, + description: 'Updated description', + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_SPECIFICATION_MODIFIED).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_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 single BUS_API_EVENT.PROJECT_UPDATED message on project bookmarks update', (done) => { + it('should send correct BUS API messages when project bookmarks updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}`) .set({ @@ -880,22 +926,32 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ resource: 'project' })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ id: project1.id })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ bookmarks: [{ title: 'title1', address: 'http://someurl.com' }] })).should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_UPDATED, - sinon.match({ updatedBy: testUtil.userIds.admin })).should.be.true; + createEventSpy.callCount.should.equal(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({ + resource: 'project', + id: project1.id, + bookmarks: [{ title: 'title1', address: 'http://someurl.com' }], + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_LINK_CREATED).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_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 BUS_API_EVENT.PROJECT_UPDATED message when project estimatedPrice is updated', (done) => { + it('should send correct BUS API messages when project estimatedPrice updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}`) .set({ @@ -903,7 +959,6 @@ describe('Project', () => { }) .send({ estimatedPrice: 123, - }) .expect(200) .end((err) => { @@ -911,14 +966,23 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.called.should.be.true; + createEventSpy.callCount.should.equal(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({ + resource: 'project', + id: project1.id, + // FIXME https://github.com/sequelize/sequelize/issues/8019 + // estimatedPrice: 123, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + done(); }); } }); }); - it('should send BUS_API_EVENT.PROJECT_UPDATED message when project actualPrice is updated', (done) => { + it('should send correct BUS API messages when project actualPrice updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}`) .set({ @@ -926,7 +990,6 @@ describe('Project', () => { }) .send({ actualPrice: 123, - }) .expect(200) .end((err) => { @@ -934,14 +997,23 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.called.should.be.true; + createEventSpy.callCount.should.equal(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({ + resource: 'project', + id: project1.id, + // FIXME https://github.com/sequelize/sequelize/issues/8019 + // actualPrice: 123, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + done(); }); } }); }); - it('should send BUS_API_EVENT.PROJECT_UPDATED message when project terms are updated', (done) => { + it('should send correct BUS API messages when project terms are updated', (done) => { request(server) .patch(`/v5/projects/${project1.id}`) .set({ @@ -949,7 +1021,6 @@ describe('Project', () => { }) .send({ terms: [1, 2, 3], - }) .expect(200) .end((err) => { @@ -957,7 +1028,15 @@ describe('Project', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.called.should.be.true; + createEventSpy.callCount.should.equal(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_UPDATED, sinon.match({ + resource: 'project', + id: project1.id, + terms: [1, 2, 3], + updatedBy: testUtil.userIds.admin, + })).should.be.true; + done(); }); } diff --git a/src/routes/timelines/update.js b/src/routes/timelines/update.js index e4c8fd5a..885d7db4 100644 --- a/src/routes/timelines/update.js +++ b/src/routes/timelines/update.js @@ -107,7 +107,8 @@ module.exports = [ req, EVENT.ROUTING_KEY.TIMELINE_UPDATED, RESOURCES.TIMELINE, - _.assign(entityToUpdate, _.pick(updated, 'id', 'updatedAt'))); + updated, + original); // Write to response res.json(updated); diff --git a/src/routes/timelines/update.spec.js b/src/routes/timelines/update.spec.js index 21b4dac3..73ae6267 100644 --- a/src/routes/timelines/update.spec.js +++ b/src/routes/timelines/update.spec.js @@ -9,7 +9,7 @@ import _ from 'lodash'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; -import { EVENT, BUS_API_EVENT, RESOURCES } from '../../constants'; +import { EVENT, BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; import busApi from '../../services/busApi'; const should = chai.should(); @@ -634,7 +634,7 @@ describe('UPDATE timeline', () => { // not testing fields separately as startDate is required parameter, // thus TIMELINE_ADJUSTED will be always sent - it('should send message BUS_API_EVENT.TIMELINE_UPDATED when timeline updated', (done) => { + it('should send correct BUS API messages when timeline updated', (done) => { request(server) .patch('/v5/timelines/1') .set({ @@ -647,11 +647,22 @@ describe('UPDATE timeline', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.TIMELINE_UPDATED, - sinon.match({ resource: RESOURCES.TIMELINE })).should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.TIMELINE_UPDATED, - sinon.match({ name: body.name })).should.be.true; + createEventSpy.callCount.should.equal(2); + + createEventSpy.calledWith(BUS_API_EVENT.TIMELINE_UPDATED, sinon.match({ + resource: RESOURCES.TIMELINE, + name: body.name, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.TIMELINE_ADJUSTED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051332, + initiatorUserId: 40051332, + })).should.be.true; + done(); }); } diff --git a/src/routes/workItems/create.js b/src/routes/workItems/create.js index 8768596e..f53ef1b4 100644 --- a/src/routes/workItems/create.js +++ b/src/routes/workItems/create.js @@ -6,7 +6,8 @@ import _ from 'lodash'; import Joi from 'joi'; import models from '../../models'; -import { EVENT } from '../../constants'; +import util from '../../util'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = require('tc-core-library-js').middleware.permissions; @@ -124,7 +125,12 @@ module.exports = [ { correlationId: req.id }, ); req.log.debug('Sending event to Kafka bus for phase product %d', newPhaseProduct.id); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, { req, created: newPhaseProduct }); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED, + RESOURCES.PHASE_PRODUCT, + newPhaseProduct); res.status(201).json(newPhaseProduct); }) diff --git a/src/routes/workItems/create.spec.js b/src/routes/workItems/create.spec.js index 5ad49082..0e7e3482 100644 --- a/src/routes/workItems/create.spec.js +++ b/src/routes/workItems/create.spec.js @@ -12,7 +12,7 @@ import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES } from '../../constants'; const should = chai.should(); @@ -314,7 +314,7 @@ describe('CREATE Work Item', () => { sandbox.restore(); }); - it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when work item created', (done) => { + it('should send correct BUS API messages when work item created', (done) => { request(server) .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems`) .set({ @@ -328,8 +328,12 @@ describe('CREATE Work Item', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, - sinon.match({ name: body.name })).should.be.false; + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + })).should.be.true; + done(); }); } diff --git a/src/routes/workItems/delete.js b/src/routes/workItems/delete.js index f8a596e5..bab03805 100644 --- a/src/routes/workItems/delete.js +++ b/src/routes/workItems/delete.js @@ -6,7 +6,8 @@ import _ from 'lodash'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; -import { EVENT } from '../../constants'; +import util from '../../util'; +import { EVENT, RESOURCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -83,7 +84,12 @@ module.exports = [ deleted, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, { req, deleted }); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED, + RESOURCES.PHASE_PRODUCT, + _.pick(deleted.toJSON(), 'id')); res.status(204).json({}); }) diff --git a/src/routes/workItems/delete.spec.js b/src/routes/workItems/delete.spec.js index a8aaaa6b..f9e98731 100644 --- a/src/routes/workItems/delete.spec.js +++ b/src/routes/workItems/delete.spec.js @@ -12,7 +12,7 @@ import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES } from '../../constants'; chai.should(); @@ -263,7 +263,7 @@ describe('DELETE Work Item', () => { sandbox.restore(); }); - it('should not send message BUS_API_EVENT.PROJECT_PLAN_UPDATED when work item removed', (done) => { + it('should send correct BUS API messages when work item removed', (done) => { request(server) .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) .set({ @@ -275,8 +275,12 @@ describe('DELETE Work Item', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PLAN_UPDATED, - sinon.match({ name: body.name })).should.be.false; + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + })).should.be.true; + done(); }); } diff --git a/src/routes/workItems/update.js b/src/routes/workItems/update.js index 96758972..d22e00bd 100644 --- a/src/routes/workItems/update.js +++ b/src/routes/workItems/update.js @@ -7,7 +7,7 @@ import Joi from 'joi'; 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, ROUTES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -108,7 +108,9 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED, RESOURCES.PHASE_PRODUCT, - _.assign(updatedProps, _.pick(updated, 'id', 'updatedAt')), + updatedValue, + previousValue, + ROUTES.WORK_ITEMS.UPDATE, ); res.json(updated); diff --git a/src/routes/workItems/update.spec.js b/src/routes/workItems/update.spec.js index 09c0019b..311e7160 100644 --- a/src/routes/workItems/update.spec.js +++ b/src/routes/workItems/update.spec.js @@ -11,7 +11,7 @@ import server from '../../app'; import models from '../../models'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); @@ -279,129 +279,187 @@ describe('UPDATE Work Item', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when name updated', (done) => { + it('should send correct BUS API messages when name updated', (done) => { request(server) - .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ - name: 'new name', - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ name: 'new name' })).should.be.true; - done(); - }); - } - }); + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + name: 'new name', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + name: 'new name', + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051331, + initiatorUserId: 40051331, + })).should.be.true; + + done(); + }); + } + }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when estimatedPrice updated', (done) => { + it('should send correct BUS API messages when estimatedPrice updated', (done) => { request(server) - .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ - estimatedPrice: 123, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ estimatedPrice: 123 })).should.be.true; - done(); - }); - } - }); + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + estimatedPrice: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + estimatedPrice: 123, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051331, + initiatorUserId: 40051331, + })).should.be.true; + + done(); + }); + } + }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when actualPrice updated', (done) => { + it('should send correct BUS API messages when actualPrice updated', (done) => { request(server) - .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ - actualPrice: 123, - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ actualPrice: 123 })).should.be.true; - done(); - }); - } - }); + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + actualPrice: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + actualPrice: 123, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051331, + initiatorUserId: 40051331, + })).should.be.true; + + done(); + }); + } + }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when details updated', (done) => { + it('should send correct BUS API messages when details updated', (done) => { request(server) - .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ - details: 'something', - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ details: 'something' })).should.be.true; - done(); - }); - } - }); + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + details: 'something', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + details: 'something', + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORKITEM_SPECIFICATION_MODIFIED) + .should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId: 1, + projectName: 'test1', + projectUrl: 'https://local.topcoder-dev.com/projects/1', + userId: 40051331, + initiatorUserId: 40051331, + })).should.be.true; + + done(); + }); + } + }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED when type updated', (done) => { + it('should send correct BUS API messages when type updated', (done) => { request(server) - .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .send({ - type: 'another type', - }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.firstCall.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, - sinon.match({ type: 'another type' })).should.be.true; - done(); - }); - } - }); + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}/workitems/${productId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .send({ + type: 'another type', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_PRODUCT_UPDATED, sinon.match({ + resource: RESOURCES.PHASE_PRODUCT, + type: 'another type', + })).should.be.true; + + done(); + }); + } + }); }); }); }); diff --git a/src/routes/works/create.spec.js b/src/routes/works/create.spec.js index 8a57ac95..52fe8708 100644 --- a/src/routes/works/create.spec.js +++ b/src/routes/works/create.spec.js @@ -11,7 +11,7 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT, RESOURCES } from '../../constants'; const should = chai.should(); @@ -26,6 +26,7 @@ const validatePhase = (resJson, expectedPhase) => { describe('CREATE work', () => { let projectId; + let projectName; let workStreamId; const memberUser = { @@ -93,6 +94,7 @@ describe('CREATE work', () => { models.Project.create(_.assign(project, { templateId: template.id })) .then((_project) => { projectId = _project.id; + projectName = _project.name; models.WorkStream.create({ name: 'Work Stream', type: 'generic', @@ -328,7 +330,7 @@ describe('CREATE work', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_CREATED when work added', (done) => { + it('should send correct BUS API messages when work added', (done) => { request(server) .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) .set({ @@ -342,9 +344,26 @@ describe('CREATE work', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, - sinon.match({ name: body.name })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_CREATED, sinon.match({ + resource: RESOURCES.PHASE, + name: body.name, + status: body.status, + budget: body.budget, + progress: body.progress, + projectId, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId, + projectName, + projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, + userId: 40051331, + initiatorUserId: 40051331, + })).should.be.true; + done(); }); } diff --git a/src/routes/works/delete.js b/src/routes/works/delete.js index 6102db20..885c8c5b 100644 --- a/src/routes/works/delete.js +++ b/src/routes/works/delete.js @@ -1,11 +1,13 @@ /** * API to delete a work */ +import _ from 'lodash'; import validate from 'express-validation'; import Joi from 'joi'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; -import { EVENT, TIMELINE_REFERENCES } from '../../constants'; +import util from '../../util'; +import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -65,7 +67,13 @@ module.exports = [ { deleted, route: TIMELINE_REFERENCES.WORK }, { correlationId: req.id }, ); - req.app.emit(EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, { req, deleted }); + + // emit event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, + RESOURCES.PHASE, + _.pick(deleted.toJSON(), 'id')); res.status(204).json({}); }).catch(err => next(err)); diff --git a/src/routes/works/delete.spec.js b/src/routes/works/delete.spec.js index 39879bfa..b57c25c7 100644 --- a/src/routes/works/delete.spec.js +++ b/src/routes/works/delete.spec.js @@ -10,7 +10,7 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT, RESOURCES } from '../../constants'; chai.should(); @@ -42,6 +42,7 @@ const expectAfterDelete = (workId, projectId, workStreamId, err, next) => { describe('DELETE work', () => { let projectId; + let projectName; let workStreamId; let workId; @@ -110,6 +111,7 @@ describe('DELETE work', () => { models.Project.create(_.assign(project, { templateId: template.id })) .then((_project) => { projectId = _project.id; + projectName = _project.name; models.WorkStream.create({ name: 'Work Stream', type: 'generic', @@ -264,7 +266,7 @@ describe('DELETE work', () => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_DELETED when work removed', (done) => { + it('should send correct BUS API messages when work removed', (done) => { request(server) .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) .set({ @@ -276,8 +278,22 @@ describe('DELETE work', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_DELETED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId, + projectName, + projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, + userId: 40051336, + initiatorUserId: 40051336, + })).should.be.true; + done(); }); } diff --git a/src/routes/works/update.js b/src/routes/works/update.js index 4042aad0..0dd343a8 100644 --- a/src/routes/works/update.js +++ b/src/routes/works/update.js @@ -8,7 +8,7 @@ import Sequelize from 'sequelize'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; -import { EVENT, RESOURCES, TIMELINE_REFERENCES } from '../../constants'; +import { EVENT, RESOURCES, TIMELINE_REFERENCES, ROUTES } from '../../constants'; const permissions = tcMiddleware.permissions; @@ -170,16 +170,21 @@ module.exports = [ .then((allPhases) => { req.log.debug('updated project phase', JSON.stringify(updated, null, 2)); + const updatedValue = updated.get({ plain: true }); + // emit original and updated project phase information req.app.services.pubsub.publish( EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, - { original: previousValue, updated, allPhases, route: TIMELINE_REFERENCES.WORK }, + { original: previousValue, updated: updatedValue, allPhases, route: TIMELINE_REFERENCES.WORK }, { correlationId: req.id }, ); - util.sendResourceToKafkaBus(req, + util.sendResourceToKafkaBus( + req, EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED, RESOURCES.PHASE, - _.clone(updated.get({ plain: true })), + updatedValue, + previousValue, + ROUTES.WORKS.UPDATE, ); res.json(updated); diff --git a/src/routes/works/update.spec.js b/src/routes/works/update.spec.js index f105c999..95286692 100644 --- a/src/routes/works/update.spec.js +++ b/src/routes/works/update.spec.js @@ -10,7 +10,7 @@ import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; -import { BUS_API_EVENT } from '../../constants'; +import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; const should = chai.should(); @@ -55,9 +55,11 @@ const validatePhase = (resJson, expectedPhase) => { describe('UPDATE work', () => { let projectId; + let projectName; let workStreamId; let workId; let workId2; + let workId3; const memberUser = { handle: testUtil.getDecodedToken(testUtil.jwts.member).handle, @@ -123,6 +125,7 @@ describe('UPDATE work', () => { models.Project.create(_.assign(project, { templateId: template.id })) .then((_project) => { projectId = _project.id; + projectName = _project.name; models.WorkStream.create({ name: 'Work Stream', type: 'generic', @@ -141,6 +144,7 @@ describe('UPDATE work', () => { models.ProjectPhase.bulkCreate(createPhases, { returning: true }).then((phases) => { workId = phases[0].id; workId2 = phases[1].id; + workId3 = phases[2].id; models.PhaseWorkStream.bulkCreate([{ phaseId: phases[0].id, workStreamId, @@ -334,20 +338,260 @@ describe('UPDATE work', () => { let createEventSpy; const sandbox = sinon.sandbox.create(); + 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'); }); + afterEach(() => { sandbox.restore(); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when duration updated', (done) => { + it('should send correct BUS API messages when spentBudget updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + spentBudget: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_PAYMENT).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when progress updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + progress: 50, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(3); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_PROGRESS).should.be.true; + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PROGRESS_MODIFIED).should.be.true; + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when details updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + details: { + text: 'something', + }, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_UPDATE_SCOPE).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when status updated (completed)', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + status: 'completed', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_TRANSITION_COMPLETED).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when status updated (active)', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId3}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + status: 'active', + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId3, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_WORK_TRANSITION_ACTIVE).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when budget updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + budget: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + done(); + }); + } + }); + }); + + it('should send correct BUS API messages when startDate updated', (done) => { + request(server) + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ + startDate: 123, + }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId, + projectName, + projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, + userId: 40051333, + initiatorUserId: 40051333, + })).should.be.true; + + done(); + }); + } + }); + }); + + + it('should send correct BUS API messages when duration updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) .set({ @@ -363,16 +607,28 @@ describe('UPDATE work', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ duration: 100 })).should.be.true; + createEventSpy.callCount.should.be.eql(2); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + duration: 100, + })).should.be.true; + + // Check Notification Service events + createEventSpy.calledWith(CONNECT_NOTIFICATION_EVENT.PROJECT_PLAN_UPDATED, sinon.match({ + projectId, + projectName, + projectUrl: `https://local.topcoder-dev.com/projects/${projectId}`, + userId: 40051333, + initiatorUserId: 40051333, + })).should.be.true; + done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when order updated', (done) => { + it('should send correct BUS API messages when order updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) .set({ @@ -388,16 +644,23 @@ describe('UPDATE work', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ order: 100 })).should.be.true; + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + + // NOTE: no other event should be called, as this phase doesn't move any other phases + done(); }); } }); }); - it('should send message BUS_API_EVENT.PROJECT_PHASE_UPDATED when endDate updated', (done) => { + it('should send correct BUS API messages when endDate updated', (done) => { request(server) .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) .set({ @@ -413,14 +676,105 @@ describe('UPDATE work', () => { done(err); } else { testUtil.wait(() => { - createEventSpy.calledOnce.should.be.true; - createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, - sinon.match({ name: body.name })).should.be.true; + createEventSpy.callCount.should.be.eql(1); + + createEventSpy.calledWith(BUS_API_EVENT.PROJECT_PHASE_UPDATED, sinon.match({ + resource: RESOURCES.PHASE, + id: workId, + updatedBy: testUtil.userIds.admin, + })).should.be.true; + done(); }); } }); }); }); + + /* describe('RabbitMQ Message topic', () => { + let updateMessageSpy; + let publishSpy; + let sandbox; + + before(async (done) => { + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + testUtil.wait(done); + }); + + beforeEach(async (done) => { + sandbox = sinon.sandbox.create(); + server.services.pubsub = new RabbitMQService(server.logger); + + // initialize RabbitMQ + server.services.pubsub.init( + config.get('rabbitmqURL'), + config.get('pubsubExchangeName'), + config.get('pubsubQueueName'), + ); + + // add project to ES index + await server.services.es.index({ + index: ES_PROJECT_INDEX, + type: ES_PROJECT_TYPE, + id: projectId, + body: { + doc: _.assign(project, { phases: [_.assign(body, { id: workId, projectId })] }), + }, + }); + + testUtil.wait(() => { + publishSpy = sandbox.spy(server.services.pubsub, 'publish'); + updateMessageSpy = sandbox.spy(messageService, 'updateTopic'); + sandbox.stub(messageService, 'getTopicByTag', () => Promise.resolve(topic)); + done(); + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + after(() => { + mockRabbitMQ(server); + }); + + it('should send message topic when work updated', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: {}, + }, + }, + }), + }); + sandbox.stub(messageService, 'getClient', () => mockHttpClient); + request(server) + .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ param: _.assign(updateBody, { budget: 123 }) }) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + publishSpy.calledOnce.should.be.true; + publishSpy.calledWith('project.phase.updated').should.be.true; + updateMessageSpy.calledTwice.should.be.true; + done(); + }); + } + }); + }); + }); */ }); }); diff --git a/src/services/busApi.js b/src/services/busApi.js index 56c5e0f8..5e716d6f 100644 --- a/src/services/busApi.js +++ b/src/services/busApi.js @@ -44,6 +44,7 @@ async function getClient() { * @return {Promise} new event promise */ function createEvent(topic, payload, logger) { + console.log(`createEvent "${topic}"`, require('util').inspect(payload)); // eslint-disable-line global-require logger.debug(`Sending message to topic ${topic}: ${JSON.stringify(payload)}`); return getClient().then((busClient) => { logger.debug('calling bus-api'); diff --git a/src/util.js b/src/util.js index 82989b0f..1e32c439 100644 --- a/src/util.js +++ b/src/util.js @@ -19,7 +19,15 @@ import Promise from 'bluebird'; import models from './models'; // import AWS from 'aws-sdk'; -import { ADMIN_ROLES, TOKEN_SCOPES, EVENT, PROJECT_MEMBER_ROLE, VALUE_TYPE, ESTIMATION_TYPE } from './constants'; +import { + ADMIN_ROLES, + TOKEN_SCOPES, + EVENT, + PROJECT_MEMBER_ROLE, + VALUE_TYPE, + ESTIMATION_TYPE, + RESOURCES, +} from './constants'; const exec = require('child_process').exec; const tcCoreLibAuth = require('tc-core-library-js').auth; @@ -525,11 +533,21 @@ _.assignIn(util, { * @param {String} key the event key * @param {String} name the resource name * @param {object} resource the resource + * @param {object} [originalResource] original resource in case resource was updated + * @param {String} [route] route which called the event (for phases and works) + * @param {Boolean}[skipNotification] if true, than event is not send to Notification Service */ - sendResourceToKafkaBus: Promise.coroutine(function* (req, key, name, resource) { // eslint-disable-line + sendResourceToKafkaBus: Promise.coroutine(function* (req, key, name, resource, originalResource, route, skipNotification) { // eslint-disable-line req.log.debug('Sending event to Kafka bus for resource %s %s', name, resource.id || resource.key); + // emit event - req.app.emit(key, { req, resource: _.assign({ resource: name }, resource) }); + req.app.emit(key, { + req, + resource: _.assign({ resource: name }, resource), + originalResource: originalResource ? _.assign({ resource: name }, originalResource) : undefined, + route, + skipNotification, + }); }), /** @@ -561,6 +579,12 @@ _.assignIn(util, { newMember, { correlationId: req.id }, ); + // emit the event + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, + RESOURCES.PROJECT_MEMBER, + newMember); return newMember; }) From 4dee61c8be3cd29191ad9846c56d17435b0d4207 Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Mon, 28 Oct 2019 21:07:12 +0800 Subject: [PATCH 20/88] =?UTF-8?q?fix:=20fix=20unit=20tests=20so=20they=20d?= =?UTF-8?q?on=E2=80=99t=20fail=20randomly.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ElasticSearch by default refreshes indexes at a interval of 1 second. There are chances that some newly added or updated documents are not available during test and some deleted documents still exist for a while, which results in random test failures. The solution contains two parts: 1. refresh the indices before making get/search requests to ES server to make sure that all ES data are visible. 2. Before some of the test cases, clear all ES data created by the previous test cases. --- migrations/elasticsearch_sync.js | 57 ++++++++++++++-------- src/events/projects/index.spec.js | 1 + src/routes/milestoneTemplates/list.spec.js | 3 ++ src/routes/milestones/delete.spec.js | 3 ++ src/routes/productCategories/list.spec.js | 4 +- src/routes/projectMembers/get.spec.js | 1 + src/routes/projectTypes/list.spec.js | 3 ++ src/routes/projects/list.spec.js | 1 + src/tests/util.js | 5 ++ src/util.js | 10 ++++ 10 files changed, 66 insertions(+), 22 deletions(-) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index d5f19ee9..2cac47ae 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -703,24 +703,39 @@ function getRequestBody(indexName) { return result; } - // first delete the index if already present -esClient.indices.delete({ - index: ES_PROJECT_INDEX, - // we would want to ignore no such index error - ignore: [404], -}) -.then(() => esClient.indices.create(getRequestBody(ES_PROJECT_INDEX))) -// Re-create timeline index -.then(() => esClient.indices.delete({ index: ES_TIMELINE_INDEX, ignore: [404] })) -.then(() => esClient.indices.create(getRequestBody(ES_TIMELINE_INDEX))) -// Re-create metadata index -.then(() => esClient.indices.delete({ index: ES_METADATA_INDEX, ignore: [404] })) -.then(() => esClient.indices.create(getRequestBody(ES_METADATA_INDEX))) -.then(() => { - console.log('elasticsearch indices synced successfully'); - process.exit(); -}) -.catch((err) => { - console.error('elasticsearch indices sync failed', err); - process.exit(); -}); +/** + * Sync elasticsearch indices. + * + * @returns {undefined} + */ +function sync() { + // first delete the index if already present + return esClient.indices.delete({ + index: ES_PROJECT_INDEX, + // we would want to ignore no such index error + ignore: [404], + }) + .then(() => esClient.indices.create(getRequestBody(ES_PROJECT_INDEX))) + // Re-create timeline index + .then(() => esClient.indices.delete({ index: ES_TIMELINE_INDEX, ignore: [404] })) + .then(() => esClient.indices.create(getRequestBody(ES_TIMELINE_INDEX))) + // Re-create metadata index + .then(() => esClient.indices.delete({ index: ES_METADATA_INDEX, ignore: [404] })) + .then(() => esClient.indices.create(getRequestBody(ES_METADATA_INDEX))); +} + +if (!module.parent) { + sync() + .then(() => { + console.log('elasticsearch indices synced successfully'); + process.exit(); + }) + .catch((err) => { + console.error('elasticsearch indices sync failed', err); + process.exit(); + }); +} + +module.exports = { + sync, +}; diff --git a/src/events/projects/index.spec.js b/src/events/projects/index.spec.js index 2b149f84..3184a422 100644 --- a/src/events/projects/index.spec.js +++ b/src/events/projects/index.spec.js @@ -94,6 +94,7 @@ describe('projectUpdatedKafkaHandler', () => { beforeEach(async () => { await testUtil.clearDb(); + await testUtil.clearES(); project = await models.Project.create({ type: 'generic', billingAccountId: 1, diff --git a/src/routes/milestoneTemplates/list.spec.js b/src/routes/milestoneTemplates/list.spec.js index 74f668ac..5a4b1567 100644 --- a/src/routes/milestoneTemplates/list.spec.js +++ b/src/routes/milestoneTemplates/list.spec.js @@ -113,6 +113,9 @@ const milestoneTemplates = [ ]; describe('LIST milestone template', () => { + before((done) => { + testUtil.clearES(done); + }); beforeEach((done) => { testUtil.clearDb() .then(() => models.ProductTemplate.bulkCreate(productTemplates)) diff --git a/src/routes/milestones/delete.spec.js b/src/routes/milestones/delete.spec.js index d1f4ada2..b00d4227 100644 --- a/src/routes/milestones/delete.spec.js +++ b/src/routes/milestones/delete.spec.js @@ -41,6 +41,9 @@ const expectAfterDelete = (timelineId, id, err, next) => { }; describe('DELETE milestone', () => { + before((done) => { + testUtil.clearES(done); + }); beforeEach((done) => { testUtil.clearDb() .then(() => { diff --git a/src/routes/productCategories/list.spec.js b/src/routes/productCategories/list.spec.js index 8bb216e5..9dcbc07b 100644 --- a/src/routes/productCategories/list.spec.js +++ b/src/routes/productCategories/list.spec.js @@ -37,7 +37,9 @@ describe('LIST product categories', () => { updatedBy: 1, }, ]; - + before((done) => { + testUtil.clearES(done); + }); beforeEach((done) => { testUtil.clearDb() .then(() => models.ProductCategory.create(productCategories[0])) diff --git a/src/routes/projectMembers/get.spec.js b/src/routes/projectMembers/get.spec.js index fd43ed2f..bde598a1 100644 --- a/src/routes/projectMembers/get.spec.js +++ b/src/routes/projectMembers/get.spec.js @@ -33,6 +33,7 @@ describe('GET project member', () => { beforeEach((done) => { testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => { // Create projects models.Project.create({ diff --git a/src/routes/projectTypes/list.spec.js b/src/routes/projectTypes/list.spec.js index 05700bd5..7074ae84 100644 --- a/src/routes/projectTypes/list.spec.js +++ b/src/routes/projectTypes/list.spec.js @@ -40,6 +40,9 @@ describe('LIST project types', () => { }, ]; + before((done) => { + testUtil.clearES(done); + }); beforeEach((done) => { testUtil.clearDb() .then(() => models.ProjectType.create(types[0])) diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js index 5ecab5c7..7b1f00e3 100644 --- a/src/routes/projects/list.spec.js +++ b/src/routes/projects/list.spec.js @@ -126,6 +126,7 @@ describe('LIST Project', () => { before(function inner(done) { this.timeout(10000); testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => { const p1 = models.Project.create({ type: 'generic', diff --git a/src/tests/util.js b/src/tests/util.js index 2996f4c9..08f0a657 100644 --- a/src/tests/util.js +++ b/src/tests/util.js @@ -1,6 +1,7 @@ /* eslint-disable max-len */ import models from '../models'; +import elasticsearchSync from '../../migrations/elasticsearch_sync'; const jwt = require('jsonwebtoken'); @@ -9,6 +10,10 @@ export default { .then(() => { if (done) done(); }), + clearES: done => elasticsearchSync.sync() + .then(() => { + if (done) done(); + }), mockHttpClient: { defaults: { headers: { common: {} } }, interceptors: { response: { use: () => {} } }, diff --git a/src/util.js b/src/util.js index 82989b0f..cca8e125 100644 --- a/src/util.js +++ b/src/util.js @@ -389,6 +389,16 @@ _.assignIn(util, { } else { esClient = new elasticsearch.Client(_.cloneDeep(config.elasticsearchConfig)); } + // during unit tests, we need to refresh the indices + // before making get/search requests to make sure all ES data can be visible. + if (process.env.NODE_ENV.toLowerCase() === 'test') { + esClient.originalSearch = esClient.search; + esClient.search = (params, cb) => esClient.indices.refresh({ index: '' }) + .then(() => esClient.originalSearch(params, cb)); // refresh index before reply + esClient.originalGet = esClient.get; + esClient.get = (params, cb) => esClient.indices.refresh({ index: '' }) + .then(() => esClient.originalGet(params, cb)); // refresh index before reply + } return esClient; }, From b137cfbc915de90a5903edce2713677db033f1e3 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 29 Oct 2019 12:12:47 +0800 Subject: [PATCH 21/88] fix: "/projects/{projectId}/permissions" endpoint follow "v5" standard - fixed response format and removed "ResponseMetadata" definition --- docs/swagger.yaml | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6ca77135..4912bf84 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2886,27 +2886,10 @@ paths: schema: title: Single work management permission response object type: object - properties: - id: - type: string - description: unique id identifying the request - version: - type: string - result: - type: object - properties: - success: - type: boolean - status: - type: integer - format: int32 - description: http status code - metadata: - $ref: '#/definitions/ResponseMetadata' - content: - type: object - example: - 'work.create': true + example: + 'work.create': true + 'workItem.edit': true + '401': description: Unauthorized schema: @@ -4804,14 +4787,6 @@ parameters: type: integer format: int64 definitions: - ResponseMetadata: - title: Metadata object for a response - type: object - properties: - totalCount: - type: integer - format: int64 - description: Total count of the objects ErrorModel: type: object properties: From 2a7b4d5ead8f5eb04501aafaf5bcbddb13788f8a Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 29 Oct 2019 16:45:49 +0800 Subject: [PATCH 22/88] fix: issue generating BUS API events when upgrading project - send "original" in PROJECT_UPDATED event as it's required for generating BUS API events --- src/routes/projectUpgrade/create.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/projectUpgrade/create.js b/src/routes/projectUpgrade/create.js index edc12152..1037f00d 100644 --- a/src/routes/projectUpgrade/create.js +++ b/src/routes/projectUpgrade/create.js @@ -208,6 +208,7 @@ async function migrateFromV2ToV3(req, project, defaultProductTemplateId, phaseNa req.app.emit(EVENT.ROUTING_KEY.PROJECT_UPDATED, { req, + original: previousValue, updated: _.assign({ resource: RESOURCES.PROJECT }, project.toJSON()), }); } From c96418e3010048820a758ad766eeb4e36614a956 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 29 Oct 2019 16:54:11 +0800 Subject: [PATCH 23/88] fix: should return 404 for deleted config - as now all the data is stored in ES, to keep this test work we have to clean ES, so the data is retrieved from DB as unit test only prepare data in DB for testing --- src/routes/orgConfig/update.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/routes/orgConfig/update.spec.js b/src/routes/orgConfig/update.spec.js index 4921c586..f25c98bb 100644 --- a/src/routes/orgConfig/update.spec.js +++ b/src/routes/orgConfig/update.spec.js @@ -87,6 +87,9 @@ describe('UPDATE organization config', () => { it('should return 404 for deleted config', (done) => { models.OrgConfig.destroy({ where: { id } }) + // we should clear ES, otherwise deleted config would be returned by ES + // TODO we should create an alternative way to test it, as all the data is "cached" in ES now + .then(() => testUtil.clearES()) .then(() => { request(server) .patch(`/v5/projects/metadata/orgConfig/${id}`) From e66babdb27e0196670bad642147946289d09aa12 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 29 Oct 2019 17:07:03 +0800 Subject: [PATCH 24/88] fix: connect.notification.project.product/workItem.update.spec event --- src/events/busApi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events/busApi.js b/src/events/busApi.js index bb6e38ae..3bc92cae 100644 --- a/src/events/busApi.js +++ b/src/events/busApi.js @@ -916,7 +916,7 @@ module.exports = (app, logger) => { if (!_.isEqual(original.details, updated.details)) { logger.debug(`Spec changed for product id ${updated.id}`); - const busApiEvent = route === 'updatePhaseProducts' + const busApiEvent = route === ROUTES.PHASE_PRODUCTS.UPDATE ? CONNECT_NOTIFICATION_EVENT.PROJECT_PRODUCT_SPECIFICATION_MODIFIED : CONNECT_NOTIFICATION_EVENT.PROJECT_WORKITEM_SPECIFICATION_MODIFIED; From 93c214e698f062debff82f6dc057ac55519fc10b Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 29 Oct 2019 17:07:14 +0800 Subject: [PATCH 25/88] fix: remove console.log --- src/services/busApi.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/busApi.js b/src/services/busApi.js index 5e716d6f..56c5e0f8 100644 --- a/src/services/busApi.js +++ b/src/services/busApi.js @@ -44,7 +44,6 @@ async function getClient() { * @return {Promise} new event promise */ function createEvent(topic, payload, logger) { - console.log(`createEvent "${topic}"`, require('util').inspect(payload)); // eslint-disable-line global-require logger.debug(`Sending message to topic ${topic}: ${JSON.stringify(payload)}`); return getClient().then((busClient) => { logger.debug('calling bus-api'); From 3b82774569dbdb8abc755a6f44e2bc941db2fa60 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 29 Oct 2019 17:39:38 +0800 Subject: [PATCH 26/88] fix: try to fix unit tests for LIST planConfig - unit tests fail on CircleCI, but don't fail locally - probably it's caused by the indexes not yet synced, so I fix ES sync method so it waits until indexes are fully created, before continues --- migrations/elasticsearch_sync.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 2cac47ae..9ded699c 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -673,6 +673,7 @@ function getRequestBody(indexName) { updateAllTypes: true, body: { mappings: { }, + refresh: 'wait_for', }, }; result.body.mappings[ES_PROJECT_TYPE] = projectMapping; @@ -683,6 +684,7 @@ function getRequestBody(indexName) { updateAllTypes: true, body: { mappings: { }, + refresh: 'wait_for', }, }; result.body.mappings[ES_METADATA_TYPE] = metadataMapping; @@ -693,6 +695,7 @@ function getRequestBody(indexName) { updateAllTypes: true, body: { mappings: { }, + refresh: 'wait_for', }, }; result.body.mappings[ES_TIMELINE_TYPE] = timelineMapping; From 1f69d50e4cd1719506b0fa7c4af63a9be975c7eb Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 30 Oct 2019 17:53:28 +0800 Subject: [PATCH 27/88] fix: removed duplicate ES index calls - as we've implemented indexing data in ES inside "project-process-es" service, we should make duplicate calls in Project Service and they have been removed - we still keep several ES index calls inside Project Service, as we do some cascading updates with a special logic for ES reindexing - indexing in such cases should be disabled in "project-process-es" repo to avoid duplication --- src/events/index.js | 63 +++++++++++++------------------ src/events/projectPhases/index.js | 5 ++- src/routes/milestones/create.js | 15 ++++---- src/routes/phases/create.js | 10 ++++- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/events/index.js b/src/events/index.js index e161ef6b..a8bb2a05 100644 --- a/src/events/index.js +++ b/src/events/index.js @@ -1,58 +1,47 @@ import { EVENT, CONNECT_NOTIFICATION_EVENT } from '../constants'; -import { projectCreatedHandler, projectUpdatedHandler, projectDeletedHandler, +import { projectCreatedHandler, projectUpdatedKafkaHandler } from './projects'; -import { projectMemberAddedHandler, projectMemberRemovedHandler, - projectMemberUpdatedHandler } from './projectMembers'; -import { projectMemberInviteCreatedHandler, - projectMemberInviteUpdatedHandler } from './projectMemberInvites'; -import { projectAttachmentRemovedHandler, - projectAttachmentUpdatedHandler, projectAttachmentAddedHandler } from './projectAttachments'; import { projectPhaseAddedHandler, projectPhaseRemovedHandler, projectPhaseUpdatedHandler } from './projectPhases'; -import { phaseProductAddedHandler, phaseProductRemovedHandler, - phaseProductUpdatedHandler } from './phaseProducts'; import { timelineAddedHandler, - timelineUpdatedHandler, - timelineRemovedHandler, timelineAdjustedKafkaHandler, } from './timelines'; import { milestoneAddedHandler, milestoneUpdatedHandler, - milestoneRemovedHandler, milestoneUpdatedKafkaHandler, } from './milestones'; +// NOTE: We use "project-processor-es" for ES indexing now. +// So I disable indexing using RabbitMQ for a transition period for most of the objects +// which don't have any special logic. +// As soon as we are sure, that "project-processor-es" works well for ES indexing, +// we should completely remove the handlers for this events. export const rabbitHandlers = { - 'project.initial': projectCreatedHandler, - [EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED]: projectCreatedHandler, - [EVENT.ROUTING_KEY.PROJECT_UPDATED]: projectUpdatedHandler, - [EVENT.ROUTING_KEY.PROJECT_DELETED]: projectDeletedHandler, - [EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED]: projectMemberAddedHandler, - [EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED]: projectMemberRemovedHandler, - [EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED]: projectMemberUpdatedHandler, - [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED]: projectMemberInviteCreatedHandler, - [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED]: projectMemberInviteUpdatedHandler, - [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED]: projectAttachmentAddedHandler, - [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED]: projectAttachmentRemovedHandler, - [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED]: projectAttachmentUpdatedHandler, - [EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED]: projectPhaseAddedHandler, - [EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED]: projectPhaseRemovedHandler, - [EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED]: projectPhaseUpdatedHandler, - [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED]: phaseProductAddedHandler, - [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED]: phaseProductRemovedHandler, - [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED]: phaseProductUpdatedHandler, + 'project.initial': projectCreatedHandler, // is only used `seedElasticsearchIndex.js` and can be removed + // [EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED]: projectCreatedHandler, + // [EVENT.ROUTING_KEY.PROJECT_UPDATED]: projectUpdatedHandler, + // [EVENT.ROUTING_KEY.PROJECT_DELETED]: projectDeletedHandler, + // [EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED]: projectMemberAddedHandler, + // [EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED]: projectMemberRemovedHandler, + // [EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED]: projectMemberUpdatedHandler, + // [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED]: projectMemberInviteCreatedHandler, + // [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED]: projectMemberInviteUpdatedHandler, + // [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED]: projectAttachmentAddedHandler, + // [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED]: projectAttachmentRemovedHandler, + // [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED]: projectAttachmentUpdatedHandler, + + // project phase handles additionally implement logic for creating associated topics in Message Service + [EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED]: projectPhaseAddedHandler, // index in ES because of cascade updates + [EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED]: projectPhaseRemovedHandler, // doesn't index in ES + [EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED]: projectPhaseUpdatedHandler, // index in ES because of cascade updates // Timeline and milestone - 'timeline.initial': timelineAddedHandler, - [EVENT.ROUTING_KEY.TIMELINE_ADDED]: timelineAddedHandler, - [EVENT.ROUTING_KEY.TIMELINE_REMOVED]: timelineRemovedHandler, - [EVENT.ROUTING_KEY.TIMELINE_UPDATED]: timelineUpdatedHandler, - [EVENT.ROUTING_KEY.MILESTONE_ADDED]: milestoneAddedHandler, - [EVENT.ROUTING_KEY.MILESTONE_REMOVED]: milestoneRemovedHandler, - [EVENT.ROUTING_KEY.MILESTONE_UPDATED]: milestoneUpdatedHandler, + 'timeline.initial': timelineAddedHandler, // is only used `seedElasticsearchIndex.js` and can be removed + [EVENT.ROUTING_KEY.MILESTONE_ADDED]: milestoneAddedHandler, // index in ES because of cascade updates + [EVENT.ROUTING_KEY.MILESTONE_UPDATED]: milestoneUpdatedHandler, // index in ES because of cascade updates }; export const kafkaHandlers = { diff --git a/src/events/projectPhases/index.js b/src/events/projectPhases/index.js index e8e33379..ec5c21b0 100644 --- a/src/events/projectPhases/index.js +++ b/src/events/projectPhases/index.js @@ -243,7 +243,7 @@ const projectPhaseUpdatedHandler = Promise.coroutine(function* (logger, msg, cha * @param {Object} msg event payload * @returns {undefined} */ -const removePhaseFromIndex = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names +const removePhaseFromIndex = Promise.coroutine(function* (logger, msg) { // eslint-disable-line func-names, no-unused-vars try { const data = JSON.parse(msg.content.toString()); const phase = _.get(data, 'deleted', {}); @@ -316,7 +316,8 @@ const removeTopics = Promise.coroutine(function* (logger, phase, route) { // esl */ const projectPhaseRemovedHandler = Promise.coroutine(function* (logger, msg, channel) { // eslint-disable-line func-names try { - yield removePhaseFromIndex(logger, msg, channel); + // NOTE We use "project-processor-es" for ES indexing now. + // yield removePhaseFromIndex(logger, msg, channel); const data = JSON.parse(msg.content.toString()); const phase = _.get(data, 'deleted', {}); const route = _.get(data, 'route'); diff --git a/src/routes/milestones/create.js b/src/routes/milestones/create.js index 0bbdf543..08cda372 100644 --- a/src/routes/milestones/create.js +++ b/src/routes/milestones/create.js @@ -102,10 +102,6 @@ module.exports = [ }), ) .then((otherUpdated) => { - // Do not send events for the updated milestones here, - // because it will make 'version conflict' error in ES. - // The order of the other milestones need to be updated in the MILESTONE_ADDED event handler - // Send event to bus req.log.debug('Sending event to RabbitMQ bus for milestone %d', result.id); req.app.services.pubsub.publish(EVENT.ROUTING_KEY.MILESTONE_ADDED, @@ -113,15 +109,20 @@ module.exports = [ { correlationId: req.id }, ); - // emit the event + // NOTE So far this logic is implemented in RabbitMQ handler of MILESTONE_ADDED + // Even though we send this event to the Kafka, the "project-processor-es" shouldn't process it. util.sendResourceToKafkaBus( req, EVENT.ROUTING_KEY.MILESTONE_ADDED, RESOURCES.MILESTONE, result); - - // emit the event for other milestone order updated + // NOTE So far this logic is implemented in RabbitMQ handler of MILESTONE_ADDED + // Even though we send these events to the Kafka, the "project-processor-es" shouldn't process them. + // + // We don't process these event in "project-processor-es" + // because it will make 'version conflict' error in ES. + // The order of the other milestones need to be updated in the PROJECT_PHASE_UPDATED event handler _.map(otherUpdated, milestone => util.sendResourceToKafkaBus( req, diff --git a/src/routes/phases/create.js b/src/routes/phases/create.js index 52071634..080e1915 100644 --- a/src/routes/phases/create.js +++ b/src/routes/phases/create.js @@ -145,14 +145,20 @@ module.exports = [ { correlationId: req.id }, ); - // emit the event + // NOTE So far this logic is implemented in RabbitMQ handler of PROJECT_PHASE_UPDATED + // Even though we send this event to the Kafka, the "project-processor-es" shouldn't process it. util.sendResourceToKafkaBus( req, EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED, RESOURCES.PHASE, newProjectPhase); - // emit the event for other phase order updated + // NOTE So far this logic is implemented in RabbitMQ handler of PROJECT_PHASE_UPDATED + // Even though we send these events to the Kafka, the "project-processor-es" shouldn't process them. + // + // We don't process these event in "project-processor-es" + // because it will make 'version conflict' error in ES. + // The order of the other milestones need to be updated in the PROJECT_PHASE_UPDATED event handler _.map(otherUpdated, phase => util.sendResourceToKafkaBus( req, From cadba464ee1f43a81f6a9691bc94ac72ddc17dab Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 30 Oct 2019 18:21:09 +0800 Subject: [PATCH 28/88] feat: bring back RabbitMQ unit tests for works - these test were missed during merging "dev" into "v5-upgrade" as we had to restore RabbitMQ events first before moving them back --- src/routes/works/create.spec.js | 91 +++++++++++++++++++++++++++ src/routes/works/delete.spec.js | 106 +++++++++++++++++++++++++++++++- src/routes/works/update.spec.js | 68 ++++++++++++-------- 3 files changed, 238 insertions(+), 27 deletions(-) diff --git a/src/routes/works/create.spec.js b/src/routes/works/create.spec.js index 52fe8708..0d3c6d67 100644 --- a/src/routes/works/create.spec.js +++ b/src/routes/works/create.spec.js @@ -7,12 +7,19 @@ import _ from 'lodash'; import chai from 'chai'; import sinon from 'sinon'; import request from 'supertest'; +import config from 'config'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; +import messageService from '../../services/messageService'; +import RabbitMQService from '../../services/rabbitmq'; +import mockRabbitMQ from '../../tests/mockRabbitMQ'; import { BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT, RESOURCES } from '../../constants'; +const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); +const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); + const should = chai.should(); const validatePhase = (resJson, expectedPhase) => { @@ -370,5 +377,89 @@ describe('CREATE work', () => { }); }); }); + + describe('RabbitMQ Message topic', () => { + let createMessageSpy; + let publishSpy; + let sandbox; + + before((done) => { + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + testUtil.wait(done); + }); + + beforeEach(async () => { + sandbox = sinon.sandbox.create(); + server.services.pubsub = new RabbitMQService(server.logger); + + // initialize RabbitMQ + server.services.pubsub.init( + config.get('rabbitmqURL'), + config.get('pubsubExchangeName'), + config.get('pubsubQueueName'), + ); + + // add project to ES index + await server.services.es.index({ + index: ES_PROJECT_INDEX, + type: ES_PROJECT_TYPE, + id: projectId, + body: { + doc: project, + }, + }); + + return new Promise(resolve => setTimeout(() => { + publishSpy = sandbox.spy(server.services.pubsub, 'publish'); + createMessageSpy = sandbox.spy(messageService, 'createTopic'); + resolve(); + }, 500)); + }); + + afterEach(() => { + sandbox.restore(); + }); + + after(() => { + mockRabbitMQ(server); + }); + + it('should send message topic when work added', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + post: () => Promise.resolve({ + status: 200, + data: { + id: 'requesterId', + version: 'v3', + result: { + success: true, + status: 200, + content: {}, + }, + }, + }), + }); + sandbox.stub(messageService, 'getClient', () => mockHttpClient); + request(server) + .post(`/v5/projects/${projectId}/workstreams/${workStreamId}/works`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .send(body) + .expect(201) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + publishSpy.calledOnce.should.be.true; + publishSpy.calledWith('project.phase.added').should.be.true; + createMessageSpy.calledTwice.should.be.true; + done(); + }); + } + }); + }); + }); }); }); diff --git a/src/routes/works/delete.spec.js b/src/routes/works/delete.spec.js index b57c25c7..e1ed3bab 100644 --- a/src/routes/works/delete.spec.js +++ b/src/routes/works/delete.spec.js @@ -6,12 +6,19 @@ import _ from 'lodash'; import request from 'supertest'; import chai from 'chai'; import sinon from 'sinon'; +import config from 'config'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; +import messageService from '../../services/messageService'; +import RabbitMQService from '../../services/rabbitmq'; +import mockRabbitMQ from '../../tests/mockRabbitMQ'; import { BUS_API_EVENT, CONNECT_NOTIFICATION_EVENT, RESOURCES } from '../../constants'; +const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); +const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); + chai.should(); const expectAfterDelete = (workId, projectId, workStreamId, err, next) => { @@ -72,7 +79,15 @@ describe('DELETE work', () => { lastActivityAt: 1, lastActivityUserId: '1', }; - + const topic = { + id: 1, + title: 'test project phase', + posts: + [{ id: 1, + type: 'post', + body: 'body', + }], + }; beforeEach((done) => { testUtil.clearDb() .then(() => { @@ -300,5 +315,94 @@ describe('DELETE work', () => { }); }); }); + + describe('RabbitMQ Message topic', () => { + let deleteTopicSpy; + let deletePostsSpy; + let publishSpy; + let sandbox; + + before((done) => { + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + testUtil.wait(done); + }); + + beforeEach(async () => { + sandbox = sinon.sandbox.create(); + server.services.pubsub = new RabbitMQService(server.logger); + + // initialize RabbitMQ + server.services.pubsub.init( + config.get('rabbitmqURL'), + config.get('pubsubExchangeName'), + config.get('pubsubQueueName'), + ); + + // add project to ES index + await server.services.es.index({ + index: ES_PROJECT_INDEX, + type: ES_PROJECT_TYPE, + id: projectId, + body: { + doc: _.assign(project, { phases: [_.assign({ + name: 'test project phase', + status: 'active', + startDate: '2018-05-15T00:00:00Z', + endDate: '2018-05-15T12:00:00Z', + budget: 20.0, + progress: 1.23456, + details: { + message: 'This can be any json', + }, + createdBy: 1, + updatedBy: 1, + projectId, + }, { id: workId, projectId })] }), + }, + }); + + return new Promise(resolve => setTimeout(() => { + publishSpy = sandbox.spy(server.services.pubsub, 'publish'); + deleteTopicSpy = sandbox.spy(messageService, 'deleteTopic'); + deletePostsSpy = sandbox.spy(messageService, 'deletePosts'); + sandbox.stub(messageService, 'getTopicByTag', () => Promise.resolve(topic)); + resolve(); + }, 500)); + }); + + afterEach(() => { + sandbox.restore(); + }); + + after(() => { + mockRabbitMQ(server); + }); + + it('should send message topic when work deleted', (done) => { + const mockHttpClient = _.merge(testUtil.mockHttpClient, { + delete: () => Promise.resolve(true), + }); + sandbox.stub(messageService, 'getClient', () => mockHttpClient); + request(server) + .delete(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(204) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + publishSpy.calledOnce.should.be.true; + publishSpy.calledWith('project.phase.removed').should.be.true; + deleteTopicSpy.calledTwice.should.be.true; + deletePostsSpy.calledTwice.should.be.true; + done(); + }); + } + }); + }); + }); }); }); diff --git a/src/routes/works/update.spec.js b/src/routes/works/update.spec.js index 95286692..ce4301b1 100644 --- a/src/routes/works/update.spec.js +++ b/src/routes/works/update.spec.js @@ -6,12 +6,19 @@ import _ from 'lodash'; import chai from 'chai'; import request from 'supertest'; import sinon from 'sinon'; +import config from 'config'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; import busApi from '../../services/busApi'; +import messageService from '../../services/messageService'; +import RabbitMQService from '../../services/rabbitmq'; +import mockRabbitMQ from '../../tests/mockRabbitMQ'; import { BUS_API_EVENT, RESOURCES, CONNECT_NOTIFICATION_EVENT } from '../../constants'; +const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); +const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); + const should = chai.should(); const body = { @@ -87,6 +94,15 @@ describe('UPDATE work', () => { lastActivityAt: 1, lastActivityUserId: '1', }; + const topic = { + id: 1, + title: 'test project phase', + posts: + [{ id: 1, + type: 'post', + body: 'body', + }], + }; beforeEach((done) => { testUtil.clearDb() .then(() => { @@ -691,17 +707,17 @@ describe('UPDATE work', () => { }); }); - /* describe('RabbitMQ Message topic', () => { + describe('RabbitMQ Message topic', () => { let updateMessageSpy; let publishSpy; let sandbox; - before(async (done) => { + before((done) => { // Wait for 500ms in order to wait for createEvent calls from previous tests to complete testUtil.wait(done); }); - beforeEach(async (done) => { + beforeEach(async () => { sandbox = sinon.sandbox.create(); server.services.pubsub = new RabbitMQService(server.logger); @@ -722,12 +738,12 @@ describe('UPDATE work', () => { }, }); - testUtil.wait(() => { + return new Promise(resolve => setTimeout(() => { publishSpy = sandbox.spy(server.services.pubsub, 'publish'); updateMessageSpy = sandbox.spy(messageService, 'updateTopic'); sandbox.stub(messageService, 'getTopicByTag', () => Promise.resolve(topic)); - done(); - }); + resolve(); + }, 500)); }); afterEach(() => { @@ -755,26 +771,26 @@ describe('UPDATE work', () => { }); sandbox.stub(messageService, 'getClient', () => mockHttpClient); request(server) - .patch(`/v4/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send({ param: _.assign(updateBody, { budget: 123 }) }) - .expect('Content-Type', /json/) - .expect(200) - .end((err) => { - if (err) { - done(err); - } else { - testUtil.wait(() => { - publishSpy.calledOnce.should.be.true; - publishSpy.calledWith('project.phase.updated').should.be.true; - updateMessageSpy.calledTwice.should.be.true; - done(); - }); - } - }); + .patch(`/v5/projects/${projectId}/workstreams/${workStreamId}/works/${workId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(_.assign(updateBody, { budget: 123 })) + .expect('Content-Type', /json/) + .expect(200) + .end((err) => { + if (err) { + done(err); + } else { + testUtil.wait(() => { + publishSpy.calledOnce.should.be.true; + publishSpy.calledWith('project.phase.updated').should.be.true; + updateMessageSpy.calledTwice.should.be.true; + done(); + }); + } + }); }); - }); */ + }); }); }); From 479dc69b68727f0d8045f4094af2a417cca723ee Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 30 Oct 2019 18:28:59 +0800 Subject: [PATCH 29/88] refactor: simpler code to wait before test --- src/routes/phases/create.spec.js | 6 ++++-- src/routes/phases/delete.spec.js | 6 ++++-- src/routes/phases/update.spec.js | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/routes/phases/create.spec.js b/src/routes/phases/create.spec.js index 94ee55c1..106cac7a 100644 --- a/src/routes/phases/create.spec.js +++ b/src/routes/phases/create.spec.js @@ -482,8 +482,10 @@ describe('Project Phases', () => { let publishSpy; let sandbox; - // Wait for 500ms in order to wait for createEvent calls from previous tests to complete - before(async () => new Promise(resolve => setTimeout(() => resolve(), 500))); + before((done) => { + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + testUtil.wait(done); + }); beforeEach(async () => { sandbox = sinon.sandbox.create(); diff --git a/src/routes/phases/delete.spec.js b/src/routes/phases/delete.spec.js index 17eea968..e619ed79 100644 --- a/src/routes/phases/delete.spec.js +++ b/src/routes/phases/delete.spec.js @@ -297,8 +297,10 @@ describe('Project Phases', () => { let publishSpy; let sandbox; - // Wait for 500ms in order to wait for createEvent calls from previous tests to complete - before(async () => new Promise(resolve => setTimeout(() => resolve(), 500))); + before((done) => { + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + testUtil.wait(done); + }); beforeEach(async () => { sandbox = sinon.sandbox.create(); diff --git a/src/routes/phases/update.spec.js b/src/routes/phases/update.spec.js index 408670ad..b8b6910b 100644 --- a/src/routes/phases/update.spec.js +++ b/src/routes/phases/update.spec.js @@ -719,8 +719,10 @@ describe('Project Phases', () => { let publishSpy; let sandbox; - // Wait for 500ms in order to wait for createEvent calls from previous tests to complete - before(async () => new Promise(resolve => setTimeout(() => resolve(), 500))); + before((done) => { + // Wait for 500ms in order to wait for createEvent calls from previous tests to complete + testUtil.wait(done); + }); beforeEach(async () => { sandbox = sinon.sandbox.create(); From a3550df18437d6909af265af7d52a3d7dab61005 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Thu, 31 Oct 2019 15:56:10 +0800 Subject: [PATCH 30/88] feat: improve debug output for BUS API events --- src/services/busApi.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/busApi.js b/src/services/busApi.js index 56c5e0f8..5916c03e 100644 --- a/src/services/busApi.js +++ b/src/services/busApi.js @@ -46,7 +46,7 @@ async function getClient() { function createEvent(topic, payload, logger) { logger.debug(`Sending message to topic ${topic}: ${JSON.stringify(payload)}`); return getClient().then((busClient) => { - logger.debug('calling bus-api'); + logger.debug(`calling bus-api for topic ${topic}`); return busClient.post('/bus/events', { topic, originator: 'project-api', @@ -54,11 +54,11 @@ function createEvent(topic, payload, logger) { 'mime-type': 'application/json', payload, }).then((resp) => { - logger.debug('Sent event to bus-api'); - logger.debug(`Sent event to bus-api [data]: ${_.get(resp, 'data')}`); - logger.debug(`Sent event to bus-api [status]: ${_.get(resp, 'status')}`); + logger.debug(`Sent event to bus-api for topic ${topic}`); + logger.debug(`Sent event to bus-api for topic ${topic} [data]: ${_.get(resp, 'data')}`); + logger.debug(`Sent event to bus-api for topic ${topic} [status]: ${_.get(resp, 'status')}`); }).catch((error) => { - logger.debug('Error sending event to bus-api'); + logger.debug(`Error sending event to bus-api for topic ${topic}`); if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx From cda34b35b43df32480a01bf8b6d986a518e00f43 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Thu, 31 Oct 2019 17:20:56 +0800 Subject: [PATCH 31/88] fix: published RabbitMQ events are not consumed Previously we removed handlers for most RabbitMQ events which caused these events to not be consumed at all. As at the moment we don't want to stop publishing them, as a workaround we consume disabled events, "ack" them and do nothing. --- src/events/index.js | 48 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/events/index.js b/src/events/index.js index a8bb2a05..c4c121b8 100644 --- a/src/events/index.js +++ b/src/events/index.js @@ -14,6 +14,24 @@ import { milestoneUpdatedKafkaHandler, } from './milestones'; +/** + * Void RabbitMQ event handler. + * It "ack"s messages which are still published but we don't want to consume. + * + * It's used to "disable" events which we don't want to handle anymore. But for a time being + * we don't want to remove the code of them until we validate that we are good without them. + * + * @param {Object} logger logger + * @param {Object} msg RabbitMQ message + * @param {Object} channel RabbitMQ channel + * @returns {Promise} nothing + */ +const voidRabbitHandler = (logger, msg, channel) => { + logger.debug('Calling void RabbitMQ handler.'); + channel.ack(msg); + return Promise.resolve(); +}; + // NOTE: We use "project-processor-es" for ES indexing now. // So I disable indexing using RabbitMQ for a transition period for most of the objects // which don't have any special logic. @@ -21,26 +39,34 @@ import { // 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]: projectCreatedHandler, - // [EVENT.ROUTING_KEY.PROJECT_UPDATED]: projectUpdatedHandler, - // [EVENT.ROUTING_KEY.PROJECT_DELETED]: projectDeletedHandler, - // [EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED]: projectMemberAddedHandler, - // [EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED]: projectMemberRemovedHandler, - // [EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED]: projectMemberUpdatedHandler, - // [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED]: projectMemberInviteCreatedHandler, - // [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED]: projectMemberInviteUpdatedHandler, - // [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED]: projectAttachmentAddedHandler, - // [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED]: projectAttachmentRemovedHandler, - // [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED]: projectAttachmentUpdatedHandler, + [EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_UPDATED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_DELETED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_CREATED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_MEMBER_INVITE_UPDATED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_UPDATED]: voidRabbitHandler, // DISABLED // project phase handles additionally implement logic for creating associated topics in Message Service [EVENT.ROUTING_KEY.PROJECT_PHASE_ADDED]: projectPhaseAddedHandler, // index in ES because of cascade updates [EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED]: projectPhaseRemovedHandler, // doesn't index in ES [EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED]: projectPhaseUpdatedHandler, // index in ES because of cascade updates + [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_ADDED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_REMOVED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.PROJECT_PHASE_PRODUCT_UPDATED]: voidRabbitHandler, // DISABLED + // Timeline and milestone 'timeline.initial': timelineAddedHandler, // is only used `seedElasticsearchIndex.js` and can be removed + [EVENT.ROUTING_KEY.TIMELINE_ADDED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.TIMELINE_REMOVED]: voidRabbitHandler, // DISABLED + [EVENT.ROUTING_KEY.TIMELINE_UPDATED]: voidRabbitHandler, // DISABLED [EVENT.ROUTING_KEY.MILESTONE_ADDED]: milestoneAddedHandler, // index in ES because of cascade updates + [EVENT.ROUTING_KEY.MILESTONE_REMOVED]: voidRabbitHandler, // DISABLED [EVENT.ROUTING_KEY.MILESTONE_UPDATED]: milestoneUpdatedHandler, // index in ES because of cascade updates }; From 41c157bbf8e223f8b6d5c6788f03619a230892af Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Thu, 31 Oct 2019 18:32:38 +0800 Subject: [PATCH 32/88] chore: added debug output for "list projects" endpoints --- src/routes/projects/list.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js index 3a03d05c..733ad73b 100755 --- a/src/routes/projects/list.js +++ b/src/routes/projects/list.js @@ -569,9 +569,11 @@ module.exports = [ return retrieveProjects(req, criteria, sort, req.query.fields) .then((result) => { if (result.rows.length === 0) { + req.log.debug('No projects found in ES'); return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) .then(r => util.setPaginationHeaders(req, res, r)); } + req.log.debug('Projects found in ES'); // set header return util.setPaginationHeaders(req, res, result); }) @@ -584,9 +586,11 @@ module.exports = [ return retrieveProjects(req, criteria, sort, req.query.fields) .then((result) => { if (result.rows.length === 0) { + req.log.debug('No projects found in ES'); return retrieveProjectsFromDB(req, criteria, sort, req.query.fields) .then(r => util.setPaginationHeaders(req, res, r)); } + req.log.debug('Projects found in ES'); return util.setPaginationHeaders(req, res, result); }) .catch(err => next(err)); From 6ebecb103f5b9face5eb0c8c503a44b0b7d94935 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Fri, 1 Nov 2019 14:36:29 +0800 Subject: [PATCH 33/88] fix: "npm run demo-data" fails if NODE_ENV is not defined --- src/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.js b/src/util.js index b17fdebe..c787be8f 100644 --- a/src/util.js +++ b/src/util.js @@ -399,7 +399,7 @@ _.assignIn(util, { } // during unit tests, we need to refresh the indices // before making get/search requests to make sure all ES data can be visible. - if (process.env.NODE_ENV.toLowerCase() === 'test') { + if (process.env.NODE_ENV === 'test') { esClient.originalSearch = esClient.search; esClient.search = (params, cb) => esClient.indices.refresh({ index: '' }) .then(() => esClient.originalSearch(params, cb)); // refresh index before reply From e94aeac9cb4287dc8d3b18762101e6acd0e4b337 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Fri, 1 Nov 2019 14:37:14 +0800 Subject: [PATCH 34/88] chore: enhance log messages --- src/routes/projectTemplates/get.js | 2 +- src/routes/projectTemplates/list.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/projectTemplates/get.js b/src/routes/projectTemplates/get.js index 593cdd29..db464bda 100644 --- a/src/routes/projectTemplates/get.js +++ b/src/routes/projectTemplates/get.js @@ -54,7 +54,7 @@ module.exports = [ }) .catch(next); } else { - req.log.debug('projectTemplates found in ES'); + req.log.debug('projectTemplate found in ES'); res.json(data[0].inner_hits.projectTemplates.hits.hits[0]._source); // eslint-disable-line no-underscore-dangle } }) diff --git a/src/routes/projectTemplates/list.js b/src/routes/projectTemplates/list.js index 81d1ee44..ce239861 100644 --- a/src/routes/projectTemplates/list.js +++ b/src/routes/projectTemplates/list.js @@ -23,7 +23,7 @@ module.exports = [ }, 'metadata') .then((data) => { if (data.projectTemplates.length === 0) { - req.log.debug('No projectTemplate found in ES'); + req.log.debug('No projectTemplates found in ES'); models.ProjectTemplate.findAll({ where: { deletedAt: { $eq: null }, From a3e0f4f6ef52fc7fd72a6b70a5018d25baf40206 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Fri, 1 Nov 2019 14:38:59 +0800 Subject: [PATCH 35/88] feat: remove "scopeChangeRequests" from "project" for now As this model is not indexed in ES we remove it for now and will bring it back after we index it in ES. --- src/routes/projects/get.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js index 3169a0d6..770f6112 100644 --- a/src/routes/projects/get.js +++ b/src/routes/projects/get.js @@ -68,10 +68,6 @@ module.exports = [ }) .then((invites) => { project.invites = invites; - return models.ScopeChangeRequest.getProjectScopeChangeRequests(projectId); - }) - .then((scopeChangeRequests) => { - project.scopeChangeRequests = scopeChangeRequests; res.status(200).json(project); }) .catch(err => next(err)); From 58c6ed4f6d8f06d7f98bdca67c3065f8dc2b6b82 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Fri, 1 Nov 2019 14:55:21 +0800 Subject: [PATCH 36/88] chore: *.local.js are ready to be used with new services Removed unnecessary configs so we use default values which are compatible with a new "project-processor-es". --- config/m2m.local.js | 6 +----- config/mock.local.js | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/config/m2m.local.js b/config/m2m.local.js index 27967c82..2f9d4bba 100644 --- a/config/m2m.local.js +++ b/config/m2m.local.js @@ -23,11 +23,7 @@ if (process.env.NODE_ENV === 'test') { idleTimeout: 1000, }, elasticsearchConfig: { - host: 'dockerhost:9200', - // target elasticsearch 2.3 version - apiVersion: '7.0', - indexName: 'projects', - docType: 'projectV5' + host: 'dockerhost:9200' }, whitelistedOriginsForUserIdAuth: "[\"\"]", }; diff --git a/config/mock.local.js b/config/mock.local.js index d9f9b786..4de2a7be 100644 --- a/config/mock.local.js +++ b/config/mock.local.js @@ -23,11 +23,7 @@ if (process.env.NODE_ENV === 'test') { idleTimeout: 1000, }, elasticsearchConfig: { - host: 'dockerhost:9200', - // target elasticsearch 2.3 version - apiVersion: '7.0', - indexName: 'projects', - docType: 'projectV5' + host: 'dockerhost:9200' }, whitelistedOriginsForUserIdAuth: "[\"\"]", }; From 4d02fa95b3fe38734f2ed747bc9f596aec80346c Mon Sep 17 00:00:00 2001 From: Rakib Ansary Saikot Date: Mon, 4 Nov 2019 11:09:33 +0600 Subject: [PATCH 37/88] Get Metadata from ES --- migrations/elasticsearch_sync.js | 27 +++ src/routes/metadata/list.js | 319 +++++++++++++++++++++++-------- src/routes/metadata/list.spec.js | 159 ++++++++++++++- 3 files changed, 424 insertions(+), 81 deletions(-) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 9ded699c..8019dda0 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -628,6 +628,33 @@ function getRequestBody(indexName) { }, }, + buildingBlocks: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + milestoneTemplates: { type: 'nested', properties: { diff --git a/src/routes/metadata/list.js b/src/routes/metadata/list.js index e254b979..1078e330 100644 --- a/src/routes/metadata/list.js +++ b/src/routes/metadata/list.js @@ -4,8 +4,13 @@ import { middleware as tcMiddleware } from 'tc-core-library-js'; import Joi from 'joi'; import validate from 'express-validation'; + import models from '../../models'; +import util from '../../util'; +const metadataProperties = ['productTemplates', 'forms', 'projectTemplates', 'planConfigs', 'priceConfigs', + 'projectTypes', 'productCategories', 'milestoneTemplates', 'buildingBlocks']; +const metadataToReturnFromES = 99999; const permissions = tcMiddleware.permissions; const schema = { @@ -68,89 +73,247 @@ function getUsedModel() { }); } +/** + * Fetch metadata from database + * + * @param {boolean} includeAllReferred when user query with includeAllReferred, return result with all used version of + * + * @return {object} metadata from database + */ +function loadMetadataFromDb(includeAllReferred) { + const query = { + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }; + const projectProductTemplateQuery = { + where: { + deletedAt: { $eq: null }, + disabled: false, + }, + attributes: { exclude: ['deletedAt', 'deletedBy'] }, + raw: true, + }; + // when user query with includeAllReferred, return result with all used version of + // Form, PriceConfig, PlanConfig + if (includeAllReferred) { + let usedModelMap; + let latestVersion; + return getUsedModel() + .then((modelUsed) => { + // found all latest version & used in project template version record + // for Form, PriceConfig, PlanConfig + usedModelMap = modelUsed; + return Promise.all([ + models.Form.latestVersionIncludeUsed(usedModelMap.form), + models.PriceConfig.latestVersionIncludeUsed(usedModelMap.priceConfig), + models.PlanConfig.latestVersionIncludeUsed(usedModelMap.planConfig), + ]); + }).then((latestVersionModels) => { + latestVersion = latestVersionModels; + return Promise.all([ + models.ProjectTemplate.findAll(projectProductTemplateQuery), + models.ProductTemplate.findAll(projectProductTemplateQuery), + models.MilestoneTemplate.findAll(query), + models.ProjectType.findAll(query), + models.ProductCategory.findAll(query), + Promise.resolve(latestVersion[0]), + Promise.resolve(latestVersion[1]), + Promise.resolve(latestVersion[2]), + ]); + }).then(queryAllResult => ({ + projectTemplates: queryAllResult[0], + productTemplates: queryAllResult[1], + milestoneTemplates: queryAllResult[2], + projectTypes: queryAllResult[3], + productCategories: queryAllResult[4], + forms: queryAllResult[5], + priceConfigs: queryAllResult[6], + planConfigs: queryAllResult[7], + }), + ); + } + return Promise.all([ + models.ProjectTemplate.findAll(projectProductTemplateQuery), + models.ProductTemplate.findAll(projectProductTemplateQuery), + models.MilestoneTemplate.findAll(query), + models.ProjectType.findAll(query), + models.ProductCategory.findAll(query), + models.Form.latestVersion(), + models.PriceConfig.latestVersion(), + models.PlanConfig.latestVersion(), + models.BuildingBlock.findAll(query), + ]).then(results => ({ + projectTemplates: results[0], + productTemplates: results[1], + milestoneTemplates: results[2], + projectTypes: results[3], + productCategories: results[4], + forms: results[5], + priceConfigs: results[6], + planConfigs: results[7], + buildingBlocks: results[8], + })); +} module.exports = [ validate(schema), permissions('metadata.list'), (req, res, next) => { - const query = { - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }; - const projectProductTemplateQuery = { - where: { - deletedAt: { $eq: null }, - disabled: false, + req.log.debug('try getting metadata from ES.'); + return util.fetchFromES(metadataProperties, { + query: { + bool: { + should: [ + { + nested: { + path: 'projectTemplates', + query: { + bool: { + must: { + match: { + 'projectTemplates.disabled': false, + }, + }, + must_not: { + match: { + 'projectTemplates.deleted': true, + }, + }, + }, + }, + inner_hits: { + size: metadataToReturnFromES, + }, + }, + }, + { + nested: { + path: 'productTemplates', + query: { + bool: { + must: { + match: { + 'productTemplates.disabled': false, + }, + }, + must_not: { + match: { + 'productTemplates.deleted': true, + }, + }, + }, + }, + inner_hits: { + size: metadataToReturnFromES, + }, + }, + }, + { + nested: { + path: 'milestoneTemplates', + query: { + match_all: {}, + }, + inner_hits: { + size: metadataToReturnFromES, + }, + }, + }, + { + nested: { + path: 'buildingBlocks', + query: { + match_all: {}, + }, + inner_hits: { + size: metadataToReturnFromES, + _source: { + excludes: [ + 'privateConfig', + ], + }, + }, + }, + }, + { + nested: { + path: 'projectTypes', + query: { + match_all: {}, + }, + inner_hits: { + size: metadataToReturnFromES, + }, + }, + }, + { + nested: { + path: 'productCategories', + query: { + match_all: {}, + }, + inner_hits: { + size: metadataToReturnFromES, + }, + }, + }, + { + nested: { + path: 'forms', + query: { + match_all: {}, + }, + inner_hits: { + size: metadataToReturnFromES, + }, + }, + }, + { + nested: { + path: 'priceConfigs', + query: { + match_all: {}, + }, + inner_hits: { + size: metadataToReturnFromES, + }, + }, + }, + { + nested: { + path: 'planConfigs', + query: { + match_all: {}, + }, + inner_hits: { + size: metadataToReturnFromES, + }, + }, + }, + ], + }, }, - attributes: { exclude: ['deletedAt', 'deletedBy'] }, - raw: true, - }; - - // when user query with includeAllReferred, return result with all used version of - // Form, PriceConfig, PlanConfig - if (req.query.includeAllReferred) { - let usedModelMap; - let latestVersion; - return getUsedModel() - .then((modelUsed) => { - // found all latest version & used in project template version record - // for Form, PriceConfig, PlanConfig - usedModelMap = modelUsed; - return Promise.all([ - models.Form.latestVersionIncludeUsed(usedModelMap.form), - models.PriceConfig.latestVersionIncludeUsed(usedModelMap.priceConfig), - models.PlanConfig.latestVersionIncludeUsed(usedModelMap.planConfig), - ]); - }).then((latestVersionModels) => { - latestVersion = latestVersionModels; - return Promise.all([ - models.ProjectTemplate.findAll(projectProductTemplateQuery), - models.ProductTemplate.findAll(projectProductTemplateQuery), - models.MilestoneTemplate.findAll(query), - models.ProjectType.findAll(query), - models.ProductCategory.findAll(query), - Promise.resolve(latestVersion[0]), - Promise.resolve(latestVersion[1]), - Promise.resolve(latestVersion[2]), - ]); - }).then((queryAllResult) => { - res.json({ - projectTemplates: queryAllResult[0], - productTemplates: queryAllResult[1], - milestoneTemplates: queryAllResult[2], - projectTypes: queryAllResult[3], - productCategories: queryAllResult[4], - forms: queryAllResult[5], - priceConfigs: queryAllResult[6], - planConfigs: queryAllResult[7], - }); - }) - .catch(next); - } - return Promise.all([ - models.ProjectTemplate.findAll(projectProductTemplateQuery), - models.ProductTemplate.findAll(projectProductTemplateQuery), - models.MilestoneTemplate.findAll(query), - models.ProjectType.findAll(query), - models.ProductCategory.findAll(query), - models.Form.latestVersion(), - models.PriceConfig.latestVersion(), - models.PlanConfig.latestVersion(), - models.BuildingBlock.findAll(query), - ]) - .then((results) => { - res.json({ - projectTemplates: results[0], - productTemplates: results[1], - milestoneTemplates: results[2], - projectTypes: results[3], - productCategories: results[4], - forms: results[5], - priceConfigs: results[6], - planConfigs: results[7], - buildingBlocks: results[8], - }); - }) - .catch(next); + }, 'metadata') + .then((data) => { + let esReturnedData = false; + const resJson = data; + const numMetadataProperties = metadataProperties.length; + for (let i = 0; i < numMetadataProperties; i += 1) { + const property = metadataProperties[i]; + if (resJson[property] != null && !Array.isArray(resJson[property])) { + resJson[property] = resJson[property].hits.hits.map(hit => hit._source); // eslint-disable-line no-underscore-dangle + if (resJson[property].length > 0) { + esReturnedData = true; + } + } + } + if (esReturnedData) { + req.log.debug('Returning results from ES'); + return res.json(resJson); + } + req.log.debug('ES returned no results'); + return loadMetadataFromDb(req.query.includeAllReferred).then(results => res.json(results)); + }) + .catch(next); }, ]; diff --git a/src/routes/metadata/list.spec.js b/src/routes/metadata/list.spec.js index 52956d08..67185f65 100644 --- a/src/routes/metadata/list.spec.js +++ b/src/routes/metadata/list.spec.js @@ -3,12 +3,18 @@ */ import chai from 'chai'; import request from 'supertest'; +import config from 'config'; +import _ from 'lodash'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; const should = chai.should(); +const expect = chai.expect; + +const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); +const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); const projectTemplates = [ { @@ -204,9 +210,36 @@ const buildingBlocks = [ }, ]; -describe('GET all metadata', () => { +const getObjToIndex = (items) => { + const toIndex = _(items).map((item) => { + const json = _.omit(item.toJSON(), 'deletedAt', 'deletedBy'); + + // setup ES markers. check these for equality with "from ES" to confirm that these records well pulled from ES + if (json.description !== undefined) { + json.description = 'from ES'; + } else if (json.info != null) { + json.info = 'from ES'; + } else if (json.details != null) { + json.details = 'from ES'; + } else if (json.config != null) { + if (json.config.sections != null) { + json.config.sections[0].description = 'from ES'; + } else if (json.hello != null) { + json.hello = 'from ES'; + } + } + // end of ES markers + + return json; + }).value(); + + return toIndex; +}; + +describe('GET all metadata from DB', () => { before((done) => { - testUtil.clearDb() + testUtil.clearES() + .then(() => testUtil.clearDb()) .then(() => models.ProjectTemplate.bulkCreate(projectTemplates)) .then(() => models.ProductTemplate.bulkCreate(productTemplates)) .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates)) @@ -215,7 +248,8 @@ describe('GET all metadata', () => { .then(() => models.Form.bulkCreate(forms)) .then(() => models.PriceConfig.bulkCreate(priceConfigs)) .then(() => models.PlanConfig.bulkCreate(planConfigs)) - .then(() => models.BuildingBlock.bulkCreate(buildingBlocks).then(() => done())); + .then(() => models.BuildingBlock.bulkCreate(buildingBlocks)) + .then(() => done()); }); after((done) => { @@ -341,3 +375,122 @@ describe('GET all metadata', () => { }); }); }); + +describe('GET all metadata from ES', () => { + before((done) => { + const esData = {}; + + testUtil.clearES() + .then(() => testUtil.clearDb()) + .then(() => models.ProjectTemplate.bulkCreate(projectTemplates, { returning: true })) + .then((created) => { esData.projectTemplates = getObjToIndex(created); }) + .then(() => models.ProductTemplate.bulkCreate(productTemplates, { returning: true })) + .then((created) => { esData.productTemplates = getObjToIndex(created); }) + .then(() => models.MilestoneTemplate.bulkCreate(milestoneTemplates, { returning: true })) + .then((created) => { esData.milestoneTemplates = getObjToIndex(created); }) + .then(() => models.ProjectType.bulkCreate(projectTypes, { returning: true })) + .then((created) => { esData.projectTypes = getObjToIndex(created); }) + .then(() => models.ProductCategory.bulkCreate(productCategories, { returning: true })) + .then((created) => { esData.productCategories = getObjToIndex(created); }) + .then(() => models.Form.bulkCreate(forms, { returning: true })) + .then((created) => { + // only index form with key `productKey 1` + const v2Form = _(created).filter(c => c.key === 'productKey 1'); + esData.forms = getObjToIndex(v2Form); + }) + .then(() => models.PriceConfig.bulkCreate(priceConfigs, { returning: true })) + .then((created) => { + // only index latest versions + const v2PriceConfigs = _(created).filter(c => c.version === 2); + esData.priceConfigs = getObjToIndex(v2PriceConfigs); + }) + .then(() => models.PlanConfig.bulkCreate(planConfigs, { returning: true })) + .then((created) => { + // only index latest versions + const v2PlanConfigs = _(created).filter(c => c.version === 2); + esData.planConfigs = getObjToIndex(v2PlanConfigs); + }) + .then(() => models.BuildingBlock.bulkCreate(buildingBlocks, { returning: true })) + .then((created) => { esData.buildingBlocks = getObjToIndex(created); }) + .then(() => server.services.es.index({ + index: ES_METADATA_INDEX, + type: ES_METADATA_TYPE, + body: esData, + })) + .then(() => done()); + }); + + after((done) => { + testUtil.clearES().then(() => testUtil.clearDb(done)); + }); + + describe('GET /projects/metadata', () => { + it('should return 200 for admin from ES', (done) => { + request(server) + .get('/v5/projects/metadata') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + const resJson = res.body; + should.exist(resJson); + resJson.projectTemplates.should.have.length(1); + resJson.projectTemplates[0].info.should.eql('from ES'); + + resJson.productTemplates.should.have.length(1); + resJson.productTemplates[0].details.should.eql('from ES'); + + resJson.milestoneTemplates.should.have.length(1); + resJson.milestoneTemplates[0].description.should.eql('from ES'); + + resJson.projectTypes.should.have.length(1); + resJson.projectTypes[0].info.should.eql('from ES'); + + resJson.productCategories.should.have.length(1); + resJson.productCategories[0].info.should.eql('from ES'); + + resJson.forms.should.have.length(1); + resJson.forms[0].key.should.eql('productKey 1'); + resJson.forms[0].config.sections.should.have.length(1); + resJson.forms[0].config.sections[0].description.should.eql('from ES'); + + resJson.planConfigs.should.have.length(1); + resJson.priceConfigs.should.have.length(1); + + resJson.forms[0].version.should.be.eql(2); + resJson.planConfigs[0].version.should.be.eql(2); + resJson.priceConfigs[0].version.should.be.eql(2); + + done(); + }); + }); + + it('should return correct building blocks for admin from ES', (done) => { + request(server) + .get('/v5/projects/metadata') + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + done(err); + } else { + const resJson = res.body; + should.exist(resJson); + should.exist(resJson.buildingBlocks); + resJson.buildingBlocks.length.should.be.eql(2); + should.not.exist(resJson.buildingBlocks[0].privateConfig); + should.not.exist(resJson.buildingBlocks[1].privateConfig); + + // ES doesn't guarantee order if sort order isn't specified. Current implementation doesn't specify sort order + expect(_.some(resJson.buildingBlocks, { key: 'key1' })).to.be.true; // eslint-disable-line no-unused-expressions + expect(_.some(resJson.buildingBlocks, { key: 'key2' })).to.be.true; // eslint-disable-line no-unused-expressions + + done(); + } + }); + }); + }); +}); From fbab3e7951ed4834499ba524741375fce6c73ca6 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 4 Nov 2019 14:28:24 +0800 Subject: [PATCH 38/88] perf: optimize GET /metadata endpoint - Optimize performance from ~2.5 seconds to ~0.15 seconds - Filter out sensitive data from BuildingBlock model --- src/routes/metadata/list.js | 213 ++++++++++-------------------------- 1 file changed, 60 insertions(+), 153 deletions(-) diff --git a/src/routes/metadata/list.js b/src/routes/metadata/list.js index 1078e330..0d2e77de 100644 --- a/src/routes/metadata/list.js +++ b/src/routes/metadata/list.js @@ -2,15 +2,24 @@ * API to list all metadata */ import { middleware as tcMiddleware } from 'tc-core-library-js'; +import _ from 'lodash'; import Joi from 'joi'; import validate from 'express-validation'; import models from '../../models'; import util from '../../util'; -const metadataProperties = ['productTemplates', 'forms', 'projectTemplates', 'planConfigs', 'priceConfigs', - 'projectTypes', 'productCategories', 'milestoneTemplates', 'buildingBlocks']; -const metadataToReturnFromES = 99999; +const metadataProperties = [ + 'productTemplates', + 'forms', + 'projectTemplates', + 'planConfigs', + 'priceConfigs', + 'projectTypes', + 'productCategories', + 'milestoneTemplates', + 'buildingBlocks', +]; const permissions = tcMiddleware.permissions; const schema = { @@ -159,160 +168,58 @@ module.exports = [ validate(schema), permissions('metadata.list'), (req, res, next) => { - req.log.debug('try getting metadata from ES.'); - return util.fetchFromES(metadataProperties, { - query: { - bool: { - should: [ - { - nested: { - path: 'projectTemplates', - query: { - bool: { - must: { - match: { - 'projectTemplates.disabled': false, - }, - }, - must_not: { - match: { - 'projectTemplates.deleted': true, - }, - }, - }, - }, - inner_hits: { - size: metadataToReturnFromES, - }, - }, - }, - { - nested: { - path: 'productTemplates', - query: { - bool: { - must: { - match: { - 'productTemplates.disabled': false, - }, - }, - must_not: { - match: { - 'productTemplates.deleted': true, - }, - }, - }, - }, - inner_hits: { - size: metadataToReturnFromES, - }, - }, - }, - { - nested: { - path: 'milestoneTemplates', - query: { - match_all: {}, - }, - inner_hits: { - size: metadataToReturnFromES, - }, - }, - }, - { - nested: { - path: 'buildingBlocks', - query: { - match_all: {}, - }, - inner_hits: { - size: metadataToReturnFromES, - _source: { - excludes: [ - 'privateConfig', - ], - }, - }, - }, - }, - { - nested: { - path: 'projectTypes', - query: { - match_all: {}, - }, - inner_hits: { - size: metadataToReturnFromES, - }, - }, - }, - { - nested: { - path: 'productCategories', - query: { - match_all: {}, - }, - inner_hits: { - size: metadataToReturnFromES, - }, - }, - }, - { - nested: { - path: 'forms', - query: { - match_all: {}, - }, - inner_hits: { - size: metadataToReturnFromES, - }, - }, - }, - { - nested: { - path: 'priceConfigs', - query: { - match_all: {}, - }, - inner_hits: { - size: metadataToReturnFromES, - }, - }, - }, - { - nested: { - path: 'planConfigs', - query: { - match_all: {}, - }, - inner_hits: { - size: metadataToReturnFromES, - }, - }, - }, - ], - }, - }, - }, 'metadata') + // As we are generally return all the data from metadata ES index we just get all the index data + // instead of creating a detailed request to get each type of object + // There are few reasons for this: + // + getting all the index works much faster than making detailed request: + // ~2.5 seconds using detailed query vs 0.15 seconds without query (including JS filtering) + // + making request we have to get data from `inner_hits` and specify `size` which is by default is `3` + // otherwise we wouldn't get all the data, but we want to get all the data + // Disadvantage: + // - we have to filter disabled Project Templates and Product Templates by JS + util.fetchFromES(null, null, 'metadata') .then((data) => { - let esReturnedData = false; - const resJson = data; - const numMetadataProperties = metadataProperties.length; - for (let i = 0; i < numMetadataProperties; i += 1) { - const property = metadataProperties[i]; - if (resJson[property] != null && !Array.isArray(resJson[property])) { - resJson[property] = resJson[property].hits.hits.map(hit => hit._source); // eslint-disable-line no-underscore-dangle - if (resJson[property].length > 0) { - esReturnedData = true; - } + const esDataToReturn = _.pick(data, metadataProperties); + // if some metadata properties are not returned from ES, then initialize such properties with empty array + // for consistency + metadataProperties.forEach((prop) => { + if (!esDataToReturn[prop]) { + esDataToReturn[prop] = []; } + }); + + // return only non-disabled Project Templates + if (esDataToReturn.projectTemplates && esDataToReturn.projectTemplates.length > 0) { + esDataToReturn.projectTemplates = _.filter(esDataToReturn.projectTemplates, { disabled: false }); } - if (esReturnedData) { - req.log.debug('Returning results from ES'); - return res.json(resJson); + + // return only non-disabled Product Templates + if (esDataToReturn.productTemplates && esDataToReturn.productTemplates.length > 0) { + esDataToReturn.productTemplates = _.filter(esDataToReturn.productTemplates, { disabled: false }); + } + + // WARNING: `BuildingBlock` model contains sensitive data! + // + // We should NEVER return `privateConfig` property for `buildingBlocks`. + // For the DB we use hooks to always clear it out, see `src/models/buildingBlock.js`. + // For the ES so far we should always remember about it and filter it out. + if (esDataToReturn.buildingBlocks && esDataToReturn.buildingBlocks.length > 0) { + esDataToReturn.buildingBlocks = _.map( + esDataToReturn.buildingBlocks, + buildingBlock => _.omit(buildingBlock, 'privateConfig'), + ); } - req.log.debug('ES returned no results'); - return loadMetadataFromDb(req.query.includeAllReferred).then(results => res.json(results)); + + // check if any data is returned from ES + const hasDataInES = _.some(esDataToReturn, propData => propData && propData.length > 0); + + if (hasDataInES) { + req.log.debug('Metadata is found in ES'); + return res.json(esDataToReturn); + } + + req.log.debug('Metadata is not found in ES'); + return loadMetadataFromDb(req.query.includeAllReferred).then(dbDataToReturn => res.json(dbDataToReturn)); }) .catch(next); }, From ee928181f0fdb8438dca9fe7ca04a5cb0ff1bc35 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 4 Nov 2019 14:49:54 +0800 Subject: [PATCH 39/88] feat: get project by id from ES Winning submission from challenge 30105640 by @yoution --- src/routes/projects/get.js | 168 +++++++++++++++++++++++++------- src/routes/projects/get.spec.js | 130 +++++++++++++++++++++++- 2 files changed, 259 insertions(+), 39 deletions(-) diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js index 770f6112..af4b264e 100644 --- a/src/routes/projects/get.js +++ b/src/routes/projects/get.js @@ -2,10 +2,14 @@ /* globals Promise */ import _ from 'lodash'; +import config from 'config'; import { middleware as tcMiddleware } from 'tc-core-library-js'; import models from '../../models'; import util from '../../util'; +const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); +const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); + /** /** * API to handle retrieving a single project by id @@ -19,46 +23,113 @@ import util from '../../util'; const permissions = tcMiddleware.permissions; const PROJECT_ATTRIBUTES = _.without(_.keys(models.Project.rawAttributes), 'utm', 'deletedAt'); const PROJECT_MEMBER_ATTRIBUTES = _.without(_.keys(models.ProjectMember.rawAttributes), 'deletedAt'); +const PROJECT_MEMBER_INVITE_ATTRIBUTES = _.without(_.keys(models.ProjectMemberInvite.rawAttributes), 'deletedAt'); +const PROJECT_ATTACHMENT_ATTRIBUTES = _.without(_.keys(models.ProjectAttachment.rawAttributes), 'deletedAt'); -module.exports = [ - permissions('project.view'), - /** - * GET projects/{projectId} - * Get a project by id - */ - (req, res, next) => { - const projectId = Number(req.params.projectId); - let fields = req.query.fields; - fields = fields ? fields.split(',') : []; - // parse the fields string to determine what fields are to be returned - fields = util.parseFields(fields, { - projects: PROJECT_ATTRIBUTES, - project_members: PROJECT_MEMBER_ATTRIBUTES, - }); - let project; - return models.Project - .findOne({ - where: { id: projectId }, - attributes: _.get(fields, 'projects', null), - raw: true, - }) - .then((_project) => { - project = _project; - if (!project) { +/** + * Parse the ES search criteria and prepare search request body + * + * @param {projctId} projectId the projectId from url + * @param {fields} fields the fields from url + * @return {Object} search request body that can be passed to .search api call + */ +const parseElasticSearchCriteria = (projectId, fields) => { + const searchCriteria = { + index: ES_PROJECT_INDEX, + type: ES_PROJECT_TYPE, + }; + + let sourceInclude; + if (_.get(fields, 'projects', null)) { + sourceInclude = _.get(fields, 'projects'); + } + if (_.get(fields, 'project_members', null)) { + const memberFields = _.get(fields, 'project_members'); + sourceInclude = sourceInclude.concat(_.map(memberFields, single => `members.${single}`)); + } + if (_.get(fields, 'project_member_invites', null)) { + const memberFields = _.get(fields, 'project_member_invites'); + sourceInclude = sourceInclude.concat(_.map(memberFields, single => `invites.${single}`)); + } + + if (_.get(fields, 'attachments', null)) { + const attachmentFields = _.get(fields, 'attachments'); + sourceInclude = sourceInclude.concat(_.map(attachmentFields, single => `attachments.${single}`)); + } + + if (sourceInclude) { + searchCriteria._sourceInclude = sourceInclude; // eslint-disable-line no-underscore-dangle + } + + + const body = { + query: { + bool: { + filter: [ + { + term: { + id: projectId, + }, + }, + ], + }, + }, + }; + searchCriteria.body = body; + return searchCriteria; +}; + +const retrieveProjectFromES = (projectId, req) => { + // parse the fields string to determine what fields are to be returned + let fields = req.query.fields; + fields = fields ? fields.split(',') : []; + fields = util.parseFields(fields, { + projects: PROJECT_ATTRIBUTES, + project_members: PROJECT_MEMBER_ATTRIBUTES, + project_member_invites: PROJECT_MEMBER_INVITE_ATTRIBUTES, + attachments: PROJECT_ATTACHMENT_ATTRIBUTES, + }); + + const searchCriteria = parseElasticSearchCriteria(projectId, fields) || {}; + return new Promise((accept, reject) => { + const es = util.getElasticSearchClient(); + es.search(searchCriteria).then((docs) => { + const rows = _.map(docs.hits.hits, single => single._source); // eslint-disable-line no-underscore-dangle + accept(rows[0]); + }).catch(reject); + }); +}; + +const retrieveProjectFromDB = (projectId, req) => { + let project; + let fields = req.query.fields; + fields = fields ? fields.split(',') : []; + fields = util.parseFields(fields, { + projects: PROJECT_ATTRIBUTES, + project_members: PROJECT_MEMBER_ATTRIBUTES, + }); + return models.Project + .findOne({ + where: { id: projectId }, + attributes: _.get(fields, 'projects', null), + raw: true, + }).then((_project) => { + project = _project; + if (!project) { // returning 404 - const apiErr = new Error(`project not found for id ${projectId}`); - apiErr.status = 404; - return Promise.reject(apiErr); - } + const apiErr = new Error(`project not found for id ${projectId}`); + apiErr.status = 404; + return Promise.reject(apiErr); + } // check context for project members - project.members = _.map(req.context.currentProjectMembers, m => _.pick(m, fields.project_members)); + project.members = _.map(req.context.currentProjectMembers, m => _.pick(m, fields.project_members)); // check if attachments field was requested - if (!req.query.fields || _.indexOf(req.query.fields, 'attachments') > -1) { - return util.getProjectAttachments(req, project.id); - } + if (!req.query.fields || _.indexOf(req.query.fields, 'attachments') > -1) { + return util.getProjectAttachments(req, project.id); + } // return null if attachments were not requested. - return Promise.resolve(null); - }) + return Promise.resolve(null); + }) .then((attachments) => { // if attachments were requested if (attachments) { @@ -68,8 +139,31 @@ module.exports = [ }) .then((invites) => { project.invites = invites; - res.status(200).json(project); - }) + return project; + }); +}; + + +module.exports = [ + permissions('project.view'), + /** + * GET projects/{projectId} + * Get a project by id + */ + (req, res, next) => { + const projectId = Number(req.params.projectId); + // parse the fields string to determine what fields are to be returned + + return retrieveProjectFromES(projectId, req).then((result) => { + if (result === undefined) { + req.log.debug('No project found in ES'); + return retrieveProjectFromDB(projectId, req); + } + req.log.debug('No project found in ES'); + return result; + }).then((project) => { + res.status(200).json(project); + }) .catch(err => next(err)); }, ]; diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js index 2731d0ab..20462562 100644 --- a/src/routes/projects/get.spec.js +++ b/src/routes/projects/get.spec.js @@ -1,16 +1,78 @@ /* eslint-disable no-unused-expressions */ +/* eslint-disable max-len */ import chai from 'chai'; import sinon from 'sinon'; import request from 'supertest'; +import config from 'config'; import models from '../../models'; import util from '../../util'; import server from '../../app'; import testUtil from '../../tests/util'; + +const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); +const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); + const should = chai.should(); +const data = [ + { + id: 5, + type: 'generic', + billingAccountId: 1, + name: 'test1', + description: 'es_project', + status: 'active', + details: { + utm: { + code: 'code1', + }, + }, + createdBy: 1, + updatedBy: 1, + lastActivityAt: 1, + lastActivityUserId: '1', + members: [ + { + id: 1, + userId: 40051331, + projectId: 1, + role: 'customer', + firstName: 'es_member_1_firstName', + lastName: 'Lastname', + handle: 'test_tourist_handle', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }, + { + id: 2, + userId: 40051332, + projectId: 1, + role: 'copilot', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }, + ], + attachments: [ + { + id: 1, + title: 'Spec', + projectId: 1, + description: 'specification', + filePath: 'projects/1/spec.pdf', + contentType: 'application/pdf', + createdBy: 1, + updatedBy: 1, + }, + ], + }, +]; + describe('GET Project', () => { + // only add project1 to es let project1; let project2; before((done) => { @@ -53,7 +115,8 @@ describe('GET Project', () => { type: 'visual_design', billingAccountId: 1, name: 'test2', - description: 'test project2', + description: 'db_project', + id: 2, status: 'draft', details: {}, createdBy: 1, @@ -62,9 +125,28 @@ describe('GET Project', () => { lastActivityUserId: '1', }).then((p) => { project2 = p; + return models.ProjectMember.create({ + userId: 40051335, + projectId: project2.id, + role: 'manager', + handle: 'manager_handle', + isPrimary: true, + createdBy: 1, + updatedBy: 1, + }); }); return Promise.all([p1, p2]) - .then(() => done()); + .then(() => server.services.es.index({ + index: ES_PROJECT_INDEX, + type: ES_PROJECT_TYPE, + id: data[0].id, + body: data[0], + })).then(() => { + // sleep for some time, let elasticsearch indices be settled + // sleep.sleep(5); + testUtil.wait(done); + // done(); + }); }); }); @@ -124,6 +206,50 @@ describe('GET Project', () => { }); }); + it('should return project with "members", "invites", and "attachments" by default when data comes from ES', (done) => { + request(server) + .get(`/v5/projects/${data[0].id}`) + .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.description.should.be.eql('es_project'); + resJson.members.should.have.lengthOf(2); + resJson.members[0].firstName.should.equal('es_member_1_firstName'); + done(); + } + }); + }); + + it('should return project with "members", "invites", and "attachments" by default when data comes from DB', (done) => { + request(server) + .get(`/v5/projects/${project2.id}`) + .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.description.should.be.eql('db_project'); + resJson.members.should.have.lengthOf(1); + resJson.members[0].role.should.equal('manager'); + done(); + } + }); + }); + it('should return the project for administrator ', (done) => { request(server) .get(`/v5/projects/${project1.id}`) From 25375fd41b80846772a9ad88d4c0c32cf1e0e0fd Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 4 Nov 2019 15:10:09 +0800 Subject: [PATCH 40/88] fix: unit tests by clearing ES index --- src/routes/phases/get.spec.js | 1 + src/routes/phases/list.spec.js | 1 + src/routes/projects/delete.spec.js | 1 + src/routes/projects/get.spec.js | 4 +++- src/routes/projects/update.spec.js | 1 + 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/routes/phases/get.spec.js b/src/routes/phases/get.spec.js index 76299814..e448f96b 100644 --- a/src/routes/phases/get.spec.js +++ b/src/routes/phases/get.spec.js @@ -42,6 +42,7 @@ describe('Project Phases', () => { before((done) => { // mocks testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => { models.Project.create({ type: 'generic', diff --git a/src/routes/phases/list.spec.js b/src/routes/phases/list.spec.js index d4d93a7a..815d4a8c 100644 --- a/src/routes/phases/list.spec.js +++ b/src/routes/phases/list.spec.js @@ -48,6 +48,7 @@ describe('Project Phases', () => { this.timeout(10000); // mocks testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => { models.Project.create({ type: 'generic', diff --git a/src/routes/projects/delete.spec.js b/src/routes/projects/delete.spec.js index 5a4447d3..8047a21d 100644 --- a/src/routes/projects/delete.spec.js +++ b/src/routes/projects/delete.spec.js @@ -36,6 +36,7 @@ describe('Project delete test', () => { let project1; beforeEach((done) => { testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => { models.Project.create({ type: 'generic', diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js index 20462562..d1bee1e0 100644 --- a/src/routes/projects/get.spec.js +++ b/src/routes/projects/get.spec.js @@ -151,7 +151,9 @@ describe('GET Project', () => { }); after((done) => { - testUtil.clearDb(done); + testUtil.clearDb() + .then(() => testUtil.clearES()) + .then(done); }); describe('GET /projects/{id}', () => { diff --git a/src/routes/projects/update.spec.js b/src/routes/projects/update.spec.js index ea49063a..91ff4e93 100644 --- a/src/routes/projects/update.spec.js +++ b/src/routes/projects/update.spec.js @@ -23,6 +23,7 @@ describe('Project', () => { let project3; beforeEach((done) => { testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => models.ProjectType.bulkCreate([ { key: 'generic', From aa14baf28c0524e1285db0629368ac877504eb11 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 4 Nov 2019 16:12:51 +0800 Subject: [PATCH 41/88] fix: unit tests by clearing ES index, part 2 --- src/routes/projects/create.spec.js | 1 + src/routes/projects/get.spec.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/routes/projects/create.spec.js b/src/routes/projects/create.spec.js index 02497846..de81a951 100644 --- a/src/routes/projects/create.spec.js +++ b/src/routes/projects/create.spec.js @@ -19,6 +19,7 @@ describe('Project create', () => { sinon.stub(RabbitMQService.prototype, 'init', () => {}); sinon.stub(RabbitMQService.prototype, 'publish', () => {}); testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => models.ProjectType.bulkCreate([ { key: 'generic', diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js index d1bee1e0..94e6e225 100644 --- a/src/routes/projects/get.spec.js +++ b/src/routes/projects/get.spec.js @@ -77,6 +77,7 @@ describe('GET Project', () => { let project2; before((done) => { testUtil.clearDb() + .then(() => testUtil.clearES()) .then(() => { const p1 = models.Project.create({ type: 'generic', From e8c0ab3afc138a900f0429837a6d9274d7098a8c Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 4 Nov 2019 18:35:21 +0800 Subject: [PATCH 42/88] chore: use locally deployed BUS API in local configs --- config/m2m.local.js | 1 + config/mock.local.js | 1 + 2 files changed, 2 insertions(+) diff --git a/config/m2m.local.js b/config/m2m.local.js index 2f9d4bba..41677f37 100644 --- a/config/m2m.local.js +++ b/config/m2m.local.js @@ -5,6 +5,7 @@ if (process.env.NODE_ENV === 'test') { config = require('./test.json'); } else { config = { + busApiUrl: "http://localhost:8002/v5", identityServiceEndpoint: "https://api.topcoder-dev.com/v3/", authSecret: 'secret', authDomain: 'topcoder-dev.com', diff --git a/config/mock.local.js b/config/mock.local.js index 4de2a7be..14fbdba4 100644 --- a/config/mock.local.js +++ b/config/mock.local.js @@ -5,6 +5,7 @@ if (process.env.NODE_ENV === 'test') { config = require('./test.json'); } else { config = { + busApiUrl: "http://localhost:8002/v5", identityServiceEndpoint: "http://dockerhost:3001/", authSecret: 'secret', authDomain: 'topcoder-dev.com', From 40e3aa5c527f78726ed83164d9e5f753d9262493 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 5 Nov 2019 12:57:18 +0800 Subject: [PATCH 43/88] chore: fix log output --- src/routes/projects/get.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js index af4b264e..84d04b7c 100644 --- a/src/routes/projects/get.js +++ b/src/routes/projects/get.js @@ -159,7 +159,7 @@ module.exports = [ req.log.debug('No project found in ES'); return retrieveProjectFromDB(projectId, req); } - req.log.debug('No project found in ES'); + req.log.debug('Project found in ES'); return result; }).then((project) => { res.status(200).json(project); From c6451c7262176d0922b423d1779cc52438fd2053 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 5 Nov 2019 12:58:09 +0800 Subject: [PATCH 44/88] fix: make resource names consistent with "project-processor-es" --- src/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index 8b66db6f..0c77c7e5 100644 --- a/src/constants.js +++ b/src/constants.js @@ -343,5 +343,5 @@ export const RESOURCES = { TIMELINE: 'timeline', MILESTONE: 'milestone', MILESTONE_TEMPLATE: 'milestone.template', - ATTACHMENT: 'project.attachment', + ATTACHMENT: 'attachment', }; From b0f46ee779b28e1dbb1568a54cc12ce042e518a7 Mon Sep 17 00:00:00 2001 From: imcaizheng Date: Wed, 6 Nov 2019 17:41:10 +0800 Subject: [PATCH 45/88] Add script for updating data in DB --- migrations/updateDataInProjectTemplate.js | 85 +++++++++++++++++++++++ package.json | 3 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 migrations/updateDataInProjectTemplate.js diff --git a/migrations/updateDataInProjectTemplate.js b/migrations/updateDataInProjectTemplate.js new file mode 100644 index 00000000..311fde4d --- /dev/null +++ b/migrations/updateDataInProjectTemplate.js @@ -0,0 +1,85 @@ +/* eslint-disable no-console */ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-await-in-loop */ +/** + * Update all records in the ProjectTemplate table. + * - inside “scope” field update “buildingBlocks..price” (for any ) to be a string if it’s not a string. + * - inside “scope” field replace all the ‘“wizard”: true’ with ‘“wizard”: {“enabled”: true}’, + * and ‘“wizard”: false’ replace with ‘“wizard”: {“enabled”: false}’. + */ +import _ from 'lodash'; +import models from '../src/models'; + +/** + * Update the wizard property of an object. + * + * @param {Object} data any object + * @returns {undefined} + */ +function updateWizardProperty(data) { + if (typeof data.wizard === 'boolean') { + data.wizard = { enabled: data.wizard }; + } +} + +/** + * Update the scope property of a projectTemplate. + * + * @param {Object} scope the scope property + * @returns {Object} the updated scope + */ +function updateScope(scope) { + // update price properties + if (scope.buildingBlocks) { + for (const key of Object.keys(scope.buildingBlocks)) { + const price = scope.buildingBlocks[key].price; + if (price !== undefined) { + if (typeof price !== 'string') { + scope.buildingBlocks[key].price = price.toString(); + } + } + } + } + // update wizard properties + updateWizardProperty(scope); + if (scope.sections) { + for (const section of scope.sections) { + updateWizardProperty(section); + if (section.subSections) { + for (const subSection of section.subSections) { + updateWizardProperty(subSection); + } + } + } + } + return scope; +} + +/** + * Update all projectTemplates. + * + * @returns {undefined} + */ +async function updateProjectTemplates() { + const projectTemplates = await models.ProjectTemplate.findAll(); + for (const projectTemplate of projectTemplates) { + if (projectTemplate.scope) { + const updatedScope = updateScope(JSON.parse(JSON.stringify(projectTemplate.scope))); + if (!_.isEqual(updatedScope, projectTemplate.scope)) { + projectTemplate.scope = updatedScope; + await projectTemplate.save(); + console.log(`updated record of ProjectTemplate with id ${projectTemplate.id}`); + } + } + } +} + +updateProjectTemplates() + .then(() => { + console.log('done!'); + process.exit(); + }).catch((err) => { + console.error('Error syncing database', err); + process.exit(1); + }); diff --git a/package.json b/package.json index 5d4dca21..ed4feab3 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "test": "NODE_ENV=test npm run lint && NODE_ENV=test npm run sync:es && NODE_ENV=test npm run sync:db && NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- --timeout 10000 --require babel-core/register $(find src -path '*spec.js*') --exit", "test:watch": "NODE_ENV=test ./node_modules/.bin/mocha -w --require babel-core/register $(find src -path '*spec.js*')", "seed": "babel-node src/tests/seed.js --presets es2015", - "demo-data": "babel-node local/seed" + "demo-data": "babel-node local/seed", + "update-data:ProjectTemplate": "babel-node migrations/updateDataInProjectTemplate.js" }, "repository": { "type": "git", From 7439062c6fc3caff7cf63db1b473381bd9e892c1 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Sun, 10 Nov 2019 13:46:31 +0800 Subject: [PATCH 46/88] feat: improved script to fix metadata to be indexed in ES Found one more inconsistence in ProductTemplate model: * - inside "template" field update all "required" properties which is not of "boolean" type to boolean. --- ...ProjectTemplate.js => fixMetadataForES.js} | 78 ++++++++++++++++++- package.json | 3 +- 2 files changed, 76 insertions(+), 5 deletions(-) rename migrations/{updateDataInProjectTemplate.js => fixMetadataForES.js} (53%) diff --git a/migrations/updateDataInProjectTemplate.js b/migrations/fixMetadataForES.js similarity index 53% rename from migrations/updateDataInProjectTemplate.js rename to migrations/fixMetadataForES.js index 311fde4d..b3436300 100644 --- a/migrations/updateDataInProjectTemplate.js +++ b/migrations/fixMetadataForES.js @@ -7,6 +7,8 @@ * - inside “scope” field update “buildingBlocks..price” (for any ) to be a string if it’s not a string. * - inside “scope” field replace all the ‘“wizard”: true’ with ‘“wizard”: {“enabled”: true}’, * and ‘“wizard”: false’ replace with ‘“wizard”: {“enabled”: false}’. + * Update all records in the ProductTemplate table. + * - inside "template" field update all "required" properties which is not of "boolean" type to boolean. */ import _ from 'lodash'; import models from '../src/models'; @@ -57,11 +59,11 @@ function updateScope(scope) { } /** - * Update all projectTemplates. + * Fix all projectTemplates. * * @returns {undefined} */ -async function updateProjectTemplates() { +async function fixProjectTemplates() { const projectTemplates = await models.ProjectTemplate.findAll(); for (const projectTemplate of projectTemplates) { if (projectTemplate.scope) { @@ -75,7 +77,77 @@ async function updateProjectTemplates() { } } -updateProjectTemplates() +/** + * Update the required property of an object. + * + * @param {Object} data any object + * @returns {undefined} + */ +function updateRequiredProperty(data) { + if (typeof data.required !== 'undefined' && typeof data.required !== 'boolean') { + if (data.required === 'false') { + data.required = false; + } else if (data.required === 'true') { + data.required = true; + } else { + throw new Error(`"required" value ${data.required} cannot be converted to boolean.`); + } + } +} + +/** + * Update the template property of a productTemplate. + * + * @param {Object} template the template property + * @returns {Object} the updated template + */ +function updateTemplate(template) { + // update wizard properties + updateRequiredProperty(template); + if (template.sections) { + for (const section of template.sections) { + updateRequiredProperty(section); + if (section.subSections) { + for (const subSection of section.subSections) { + updateRequiredProperty(subSection); + } + } + } + } + return template; +} + +/** + * Fix all productTemplates. + * + * @returns {undefined} + */ +async function fixProductTemplates() { + const productTemplates = await models.ProductTemplate.findAll(); + + for (const productTemplate of productTemplates) { + if (productTemplate.template) { + const updatedTemplate = updateTemplate(JSON.parse(JSON.stringify(productTemplate.template))); + if (!_.isEqual(updatedTemplate, productTemplate.template)) { + productTemplate.template = updatedTemplate; + await productTemplate.save(); + console.log(`updated record of ProductTemplate with id ${productTemplate.id}`); + } + } + } +} + +/** + * Fix all metadata models. + * + * @returns {undefined} + */ +async function fixMetadataForES() { + await fixProjectTemplates(); + await fixProductTemplates(); +} + +fixMetadataForES() .then(() => { console.log('done!'); process.exit(); diff --git a/package.json b/package.json index ed4feab3..5d4dca21 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "test": "NODE_ENV=test npm run lint && NODE_ENV=test npm run sync:es && NODE_ENV=test npm run sync:db && NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- --timeout 10000 --require babel-core/register $(find src -path '*spec.js*') --exit", "test:watch": "NODE_ENV=test ./node_modules/.bin/mocha -w --require babel-core/register $(find src -path '*spec.js*')", "seed": "babel-node src/tests/seed.js --presets es2015", - "demo-data": "babel-node local/seed", - "update-data:ProjectTemplate": "babel-node migrations/updateDataInProjectTemplate.js" + "demo-data": "babel-node local/seed" }, "repository": { "type": "git", From 7646ec13f43b979367ae5c0d9611ee7642a9a1cc Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Sun, 10 Nov 2019 13:50:07 +0800 Subject: [PATCH 47/88] feat: added ES mapping for the Product Template's "options.value"s We have to define mapping for "value" field of "options" so we can index inside any data which can be converted as strings like numbers. Otherwise indexing numbers would cause error during indexing like `productTemplates.template.sections.subSections.questions.options.value] of different type, current_type [string], merged_type [long]"`. Ref issue #409 --- migrations/elasticsearch_sync.js | 36 +++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 8019dda0..6591a7a8 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -347,6 +347,36 @@ function getRequestBody(indexName) { }, }; + // form config can be present inside 3 models, so we reuse it + const formConfig = { + type: 'object', + properties: { + sections: { + type: 'nested', + properties: { + subSections: { + type: 'nested', + properties: { + questions: { + type: 'nested', + properties: { + options: { + type: 'nested', + properties: { + value: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + const metadataMapping = { _all: { enabled: false }, properties: { @@ -374,9 +404,7 @@ function getRequestBody(indexName) { id: { type: 'long', }, - scope: { - type: 'object', - }, + scope: formConfig, form: { type: 'object', }, @@ -412,6 +440,7 @@ function getRequestBody(indexName) { type: 'string', index: 'not_analyzed', }, + config: formConfig, version: { type: 'integer', }, @@ -544,6 +573,7 @@ function getRequestBody(indexName) { name: { type: 'string', }, + template: formConfig, productKey: { type: 'string', index: 'not_analyzed', From b36d5cb6130b5d4c789ae51d3dc2407b5032d451 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Sun, 10 Nov 2019 13:56:02 +0800 Subject: [PATCH 48/88] feat: command to recreate only some ES indexes instead of all Now script to sync indexes may be used to sync only some indexes. General usage is the next one: "npm run sync:es -- --index-name metadata". Also, if "NODE_ENV !== 'development'" this script now doesn't let sync all the indexes, and only allow syncing one index per time which is explicitly defined by name. For the safer migration to V5 added temporary command "npm run sync:es:metadata" to sync "metadata" index only. --- migrations/elasticsearch_sync.js | 51 ++++++++++++++++++++------------ package.json | 1 + 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 6591a7a8..f4c01a33 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -21,6 +21,8 @@ const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType'); const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); +const allowedIndexes = [ES_PROJECT_INDEX, ES_TIMELINE_INDEX, ES_METADATA_INDEX]; + // create new elasticsearch client // the client modifies the config object, so always passed the cloned object const esClient = util.getElasticSearchClient(); @@ -766,33 +768,44 @@ function getRequestBody(indexName) { /** * Sync elasticsearch indices. * - * @returns {undefined} + * @param {String} [indexName] index name to sync, if it's not define, then all indexes are recreated + * + * @returns {Promise} resolved when sync is complete */ -function sync() { - // first delete the index if already present - return esClient.indices.delete({ - index: ES_PROJECT_INDEX, - // we would want to ignore no such index error - ignore: [404], - }) - .then(() => esClient.indices.create(getRequestBody(ES_PROJECT_INDEX))) - // Re-create timeline index - .then(() => esClient.indices.delete({ index: ES_TIMELINE_INDEX, ignore: [404] })) - .then(() => esClient.indices.create(getRequestBody(ES_TIMELINE_INDEX))) - // Re-create metadata index - .then(() => esClient.indices.delete({ index: ES_METADATA_INDEX, ignore: [404] })) - .then(() => esClient.indices.create(getRequestBody(ES_METADATA_INDEX))); +async function sync(indexName) { + if (indexName && allowedIndexes.indexOf(indexName) === -1) { + throw new Error(`Index "${indexName}" is not supported.`); + } + const indexesToSync = indexName ? [indexName] : allowedIndexes; + + for (let i = 0; i < indexesToSync.length; i += 1) { + const indexToSync = indexesToSync[i]; + + console.log(`Deleting "${indexToSync}" index...`); + await esClient.indices.delete({ index: indexToSync, ignore: [404] }); // eslint-disable-line no-await-in-loop + console.log(`Creating "${indexToSync}" index...`); + await esClient.indices.create(getRequestBody(indexToSync)); // eslint-disable-line no-await-in-loop + } } if (!module.parent) { - sync() + // if we pass index name in command line arguments, then sync only that index + const indexName = process.argv[2] === '--index-name' && process.argv[3] ? process.argv[3] : undefined; + + if (process.env.NODE_ENV !== 'development' && !indexName) { + console.error('Error. "--index-name" should be provided when run this command in non-development environment.'); + console.error('Example usage: "$ npm run sync:es -- --index-name metadata"'); + process.exit(1); + } + + sync(indexName) .then(() => { - console.log('elasticsearch indices synced successfully'); + console.log('ElasticSearch indices synced successfully.'); process.exit(); }) .catch((err) => { - console.error('elasticsearch indices sync failed', err); - process.exit(); + console.error('ElasticSearch indices sync failed: ', err); + process.exit(1); }); } diff --git a/package.json b/package.json index 5d4dca21..81844494 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "build": "babel src -d dist --presets es2015 --copy-files", "sync:db": "./node_modules/.bin/babel-node migrations/sync.js", "sync:es": "./node_modules/.bin/babel-node migrations/elasticsearch_sync.js", + "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", "prestart": "npm run -s build", "start": "node dist", From f6f5e99c2413c4c4a9402239e29c073bc0193ba2 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Sun, 10 Nov 2019 13:59:50 +0800 Subject: [PATCH 49/88] feat: 2 ways of indexing metadata in ES Added two scripts which could be used to index data in ES. 1. `indexMetadataDirectly.js` indexes metadata very fast using one call of the index. Also, added a reusable method `es.indexMetadata` which can used in other places like unit tests to index metadata to DB from ES. 2. `indexMetadataByProcessor` this script is more to development and testing purposes. It indexes metadata by sending Kafka events to "project-processor-es". It works much slower and doesn't give feedback about errors. But it index the metadata exactly the same way it's being indexed in real-life usage. --- .../helpers/indexMetadataByProcessor.js | 98 +++++++++++++++++++ migrations/helpers/indexMetadataDirectly.js | 21 ++++ package.json | 1 + src/utils/es.js | 75 ++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 migrations/helpers/indexMetadataByProcessor.js create mode 100644 migrations/helpers/indexMetadataDirectly.js create mode 100644 src/utils/es.js diff --git a/migrations/helpers/indexMetadataByProcessor.js b/migrations/helpers/indexMetadataByProcessor.js new file mode 100644 index 00000000..c0689605 --- /dev/null +++ b/migrations/helpers/indexMetadataByProcessor.js @@ -0,0 +1,98 @@ +/* eslint-disable no-console */ +/** + * Sync metadata models from DB to ES using "project-processor-es". + * + * The main purpose of this script is to use during development to validate that all metadata can be + * correctly indexed using "project-processor-es". + * + * Advantage: It sync DB data to ES using "project-processor-es" as if were adding them using API. + * So the result of this process is closer to what we would get if we added these objects + * using API. + * + * Disadvantage: We don't know when the syncing process is done and if there are any errors or no. + * To get this information we have to watch the log of "project-processor-es". + */ +import _ from 'lodash'; +import models from '../../src/models'; +import { RESOURCES } from '../../src/constants'; +import { createEvent } from '../../src/services/busApi'; + +const modelConfigs = { + ProjectTemplate: { + indexProperty: 'projectTemplates', + resource: RESOURCES.PROJECT_TEMPLATE, + }, + ProductTemplate: { + indexProperty: 'productTemplates', + resource: RESOURCES.PRODUCT_TEMPLATE, + }, + ProjectType: { + indexProperty: 'projectTypes', + resource: RESOURCES.PROJECT_TYPE, + }, + ProductCategory: { + indexProperty: 'productCategories', + resource: RESOURCES.PRODUCT_CATEGORY, + }, + MilestoneTemplate: { + indexProperty: 'milestoneTemplates', + resource: RESOURCES.MILESTONE_TEMPLATE, + }, + OrgConfig: { + indexProperty: 'orgConfigs', + resource: RESOURCES.ORG_CONFIG, + }, + Form: { + indexProperty: 'forms', + resource: RESOURCES.FORM_REVISION, + }, + PlanConfig: { + indexProperty: 'planConfigs', + resource: RESOURCES.PLAN_CONFIG_REVISION, + }, + PriceConfig: { + indexProperty: 'priceConfigs', + resource: RESOURCES.PRICE_CONFIG_REVISION, + }, + // This model is not yet supported by "project-processor-es" + // BuildingBlock: { + // indexProperty: 'buildingBlocks', + // resource: RESOURCES. + // }, +}; + +/** + * Sync metadata index + * + * @returns {Promise} promise when all is done + */ +async function syncMetadataIndex() { + const modelNames = _.keys(modelConfigs); + + for (let i = 0; i < modelNames.length; i += 1) { + const modelName = modelNames[i]; + const modelConfig = modelConfigs[modelName]; + const records = await models[modelName].findAll({ raw: true }); // eslint-disable-line no-await-in-loop + + console.log(`Syncing ${records.length} records for model ${modelName}...`); + + await Promise.all( // eslint-disable-line no-await-in-loop + records.map(record => + createEvent( + 'project.notification.create', + _.assign({ resource: modelConfig.resource }, record), + console, + ), + ), + ); + } +} + +syncMetadataIndex() + .then(() => { + console.log('Done. The event to sync data in ES have been published to Bus API!'); + process.exit(); + }).catch((err) => { + console.error('Error', err); + process.exit(1); + }); diff --git a/migrations/helpers/indexMetadataDirectly.js b/migrations/helpers/indexMetadataDirectly.js new file mode 100644 index 00000000..6f50a7dc --- /dev/null +++ b/migrations/helpers/indexMetadataDirectly.js @@ -0,0 +1,21 @@ +/* eslint-disable no-console */ +/** + * Sync metadata models from DB to ES using direct call of es client. + * + * Advantage: It syncs data fast and we know if the process was successful or no. + * + * Disadvantage: As in real life data is indexing using "project-processor-es", it may happen + * that our custom implementation is somehow different. Though it shouldn't. + */ +import { indexMetadata } from '../../src/utils/es'; + +console.log('Indexing metadata from DB...'); + +indexMetadata() + .then(() => { + console.log('Done!'); + process.exit(); + }).catch((err) => { + console.error('Error', err); + process.exit(1); + }); diff --git a/package.json b/package.json index 81844494..c1cff380 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "sync:es": "./node_modules/.bin/babel-node migrations/elasticsearch_sync.js", "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", "prestart": "npm run -s build", "start": "node dist", "startKafkaConsumers": "npm run -s build && node dist/index-kafka.js", diff --git a/src/utils/es.js b/src/utils/es.js new file mode 100644 index 00000000..a56dab6e --- /dev/null +++ b/src/utils/es.js @@ -0,0 +1,75 @@ +/** + * Methods to index data from DB in ES. + */ +import _ from 'lodash'; +import config from 'config'; +import util from '../util'; +import models from '../models'; + +const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); +const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); + +const eClient = util.getElasticSearchClient(); + +const modelConfigs = { + ProjectTemplate: { + indexProperty: 'projectTemplates', + }, + ProductTemplate: { + indexProperty: 'productTemplates', + }, + ProjectType: { + indexProperty: 'projectTypes', + }, + ProductCategory: { + indexProperty: 'productCategories', + }, + MilestoneTemplate: { + indexProperty: 'milestoneTemplates', + }, + OrgConfig: { + indexProperty: 'orgConfigs', + }, + Form: { + indexProperty: 'forms', + }, + PlanConfig: { + indexProperty: 'planConfigs', + }, + PriceConfig: { + indexProperty: 'priceConfigs', + }, + BuildingBlock: { + indexProperty: 'buildingBlocks', + }, +}; + +/** + * Index metadata models defined by `modelConfigs` + * + * @returns {Promise} esClient.index result + */ +async function indexMetadata() { + const modelNames = _.keys(modelConfigs); + const body = {}; + + for (let i = 0; i < modelNames.length; i += 1) { + const modelName = modelNames[i]; + const modelConfig = modelConfigs[modelName]; + const records = await models[modelName].findAll({ raw: true }); // eslint-disable-line no-await-in-loop + + body[modelConfig.indexProperty] = records; + } + + // TODO add check that there is no data in ES_METADATA_INDEX yet, or throw an error + + return eClient.index({ + index: ES_METADATA_INDEX, + type: ES_METADATA_TYPE, + body, + }); +} + +module.exports = { + indexMetadata, +}; From 939fe0088183dbbb77a52d63a6940e99ae5d8db6 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Sun, 10 Nov 2019 14:22:09 +0800 Subject: [PATCH 50/88] fix: allow re-create all ES indexes in "test" env --- migrations/elasticsearch_sync.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index f4c01a33..875e716b 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -792,7 +792,9 @@ if (!module.parent) { // if we pass index name in command line arguments, then sync only that index const indexName = process.argv[2] === '--index-name' && process.argv[3] ? process.argv[3] : undefined; - if (process.env.NODE_ENV !== 'development' && !indexName) { + // to avoid accidental resetting of all indexes in PROD, enforce explicit defining of index name if not in + // development or test environment + if (['development', 'test'].indexOf(process.env.NODE_ENV) === -1 && !indexName) { console.error('Error. "--index-name" should be provided when run this command in non-development environment.'); console.error('Example usage: "$ npm run sync:es -- --index-name metadata"'); process.exit(1); From 7fd19567a039fd0010166d44d4e4f650262f70ef Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Sun, 10 Nov 2019 14:23:14 +0800 Subject: [PATCH 51/88] fix: remove unnecessary "refresh: 'wait_for'" from ES sync script This property is not supported by "create" index operation. --- migrations/elasticsearch_sync.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 875e716b..88ec0b73 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -732,7 +732,6 @@ function getRequestBody(indexName) { updateAllTypes: true, body: { mappings: { }, - refresh: 'wait_for', }, }; result.body.mappings[ES_PROJECT_TYPE] = projectMapping; @@ -743,7 +742,6 @@ function getRequestBody(indexName) { updateAllTypes: true, body: { mappings: { }, - refresh: 'wait_for', }, }; result.body.mappings[ES_METADATA_TYPE] = metadataMapping; @@ -754,7 +752,6 @@ function getRequestBody(indexName) { updateAllTypes: true, body: { mappings: { }, - refresh: 'wait_for', }, }; result.body.mappings[ES_TIMELINE_TYPE] = timelineMapping; From 9c4780b5a8c0848b950ca0130f7faa4cac293958 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Sun, 10 Nov 2019 14:46:08 +0800 Subject: [PATCH 52/88] feat: improved script to fix metadata to be indexed in ES Found one more inconsistence in ProductTemplate model in PROD data: * - inside "template" field update all "questions[].required" properties which is not of "boolean" type to boolean. --- migrations/fixMetadataForES.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/migrations/fixMetadataForES.js b/migrations/fixMetadataForES.js index b3436300..bc28aba7 100644 --- a/migrations/fixMetadataForES.js +++ b/migrations/fixMetadataForES.js @@ -110,6 +110,11 @@ function updateTemplate(template) { if (section.subSections) { for (const subSection of section.subSections) { updateRequiredProperty(subSection); + if (subSection.questions) { + for (const question of subSection.questions) { + updateRequiredProperty(question); + } + } } } } From c44d2e590585984a24be71dbc415b0e0e56a36b7 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Sun, 10 Nov 2019 14:48:29 +0800 Subject: [PATCH 53/88] chore: generate "package-lock.json" file It was previously removed during merging "dev" branch into "v5-upgrade". --- package-lock.json | 9494 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 9494 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..586f817e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9494 @@ +{ + "name": "tc-projects-service", + "version": "1.4.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@segment/loosely-validate-event": { + "version": "1.1.2", + "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" + } + }, + "@types/bluebird": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-JjNHCk6r6aR82aRf2yDtX5NAe8o=" + }, + "@types/body-parser": { + "version": "1.17.1", + "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": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.2", + "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/express-jwt": { + "version": "0.0.42", + "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-serve-static-core": { + "version": "4.16.11", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.11.tgz", + "integrity": "sha512-K8d2M5t3tBQimkyaYTXxtHYyoJPUEhy2/omVRnTAKw5FEdT+Ft6lTaTOpoJdHeG+mIwQXXtqiTcYZ6IR8LTzjQ==", + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/express-unless": { + "version": "0.5.1", + "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/lodash": { + "version": "4.14.146", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.146.tgz", + "integrity": "sha512-JzJcmQ/ikHSv7pbvrVNKJU5j9jL9VLf3/gqs048CEnBVVVEv4kve3vLxoPHGvclutS+Il4SBIuQQ087m1eHffw==" + }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" + }, + "@types/node": { + "version": "12.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.7.tgz", + "integrity": "sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "@types/serve-static": { + "version": "1.13.3", + "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": "*" + } + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "agentkeepalive": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "amqplib": { + "version": "0.5.5", + "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" + } + }, + "analytics-node": { + "version": "2.4.1", + "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" + } + }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "app-module-path": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-1.1.0.tgz", + "integrity": "sha1-pqxTaEUPIJufW4bpo+Smq2/nUxw=" + }, + "append-transform": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", + "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "dev": true, + "requires": { + "default-require-extensions": "^1.0.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-listener": { + "version": "0.6.10", + "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" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "auth0-js": { + "version": "9.11.3", + "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.11.3.tgz", + "integrity": "sha512-86EGbaXPHBuyYPPPpvkckH7rCnEgS14DHsK64v2tb4ph4NsZ+peW6pjwBHkOdz4Ytd/ibhGTYCEbA9xdHWSqiA==", + "requires": { + "base64-js": "^1.3.0", + "idtoken-verifier": "^1.4.1", + "js-cookie": "^2.2.0", + "qs": "^6.7.0", + "superagent": "^3.8.3", + "url-join": "^4.0.1", + "winchan": "^0.2.2" + } + }, + "aws-sdk": { + "version": "2.568.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.568.0.tgz", + "integrity": "sha512-jPvhiJV2iLyWbJJDM01gvUCzeChWUeRMkIr6dsHu+leH2QnzvGNunTwMGculKE1jouXatajZEoA9bdqfosranw==", + "requires": { + "buffer": "^4.9.1", + "events": "^1.1.1", + "ieee754": "^1.1.13", + "jmespath": "^0.15.0", + "querystring": "^0.2.0", + "sax": "^1.2.1", + "url": "^0.10.3", + "uuid": "^3.3.2", + "xml2js": "^0.4.19" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, + "babel-cli": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "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" + } + } + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + } + } + }, + "babel-eslint": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", + "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-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-add-module-exports": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz", + "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=", + "dev": true + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "^0.10.0" + } + }, + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } + } + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + } + } + }, + "babel-preset-es2015": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", + "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-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-runtime": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.6.1.tgz", + "integrity": "sha1-eIuUtvY04luRvWxd9y1GdFevsAA=", + "requires": { + "core-js": "^2.1.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "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" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", + "requires": { + "precond": "0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-protocol": { + "version": "3.1.1", + "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" + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bitsyntax": { + "version": "0.1.0", + "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" + } + }, + "bluebird": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "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" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "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" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "optional": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + } + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "bunyan": { + "version": "1.8.12", + "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" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "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" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "deep-eql": "^0.1.3", + "type-detect": "^1.0.0" + } + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "check-more-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.3.0.tgz", + "integrity": "sha1-uDl8adySo+ZF8YkywEWwnHRBnsQ=", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "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" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, + "cli-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "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" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "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" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "cls-bluebird": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "requires": { + "is-bluebird": "^1.0.2", + "shimmer": "^1.1.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "codependency": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/codependency/-/codependency-0.1.4.tgz", + "integrity": "sha1-0XY6tyZL1wyR2WJumIYtN5K/jUo=", + "requires": { + "semver": "5.0.1" + }, + "dependencies": { + "semver": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.1.tgz", + "integrity": "sha1-n7P0AE+QDYPEeWj+QvdYPgWDLMk=" + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "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" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "component-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.1.tgz", + "integrity": "sha1-ikeQFwAjjk/DIml3EjAibyS0Fak=" + }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "requires": { + "mime-db": ">= 1.40.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "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" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "config": { + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/config/-/config-1.31.0.tgz", + "integrity": "sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA==", + "requires": { + "json5": "^1.0.1" + } + }, + "config-chain": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "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" + } + }, + "connection-parse": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/connection-parse/-/connection-parse-0.0.7.tgz", + "integrity": "sha1-GOcxiqsGppkmc3KxDFIm0locmmk=" + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "continuation-local-storage": { + "version": "3.2.1", + "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" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", + "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.5", + "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" + } + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-js": { + "version": "3.1.9-1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", + "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, + "crypto-token": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto-token/-/crypto-token-1.0.1.tgz", + "integrity": "sha1-J8ZIL687Y8L12hFXf4MENG/nl6U=" + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", + "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", + "dev": true, + "requires": { + "strip-bom": "^2.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "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" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dottie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.1.tgz", + "integrity": "sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw==" + }, + "dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "optional": true, + "requires": { + "nan": "^2.14.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "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" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "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" + } + }, + "editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "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" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + } + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "elasticsearch": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-16.5.0.tgz", + "integrity": "sha512-9YbmU2AtM/kQdmp96EI5nu2bjxowdarV6IsKmcS+jQowJ3mhG98J1DCVOtEKuFvsnNaLyKD3aPbCAmb72+WX3w==", + "requires": { + "agentkeepalive": "^3.4.1", + "chalk": "^1.0.0", + "lodash": "^4.17.10" + } + }, + "emitter-listener": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", + "requires": { + "shimmer": "^1.2.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.52", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.52.tgz", + "integrity": "sha512-bWCbE9fbpYQY4CU6hJbJ1vSz70EClMlDgJ7BmwI+zEJhxrwjesZRPglGJlsZhu0334U3hI+gaspwksH9IGD6ag==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.2", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "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" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "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" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "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" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "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" + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0" + } + } + } + }, + "eslint-config-airbnb-base": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.3.2.tgz", + "integrity": "sha512-/fhjt/VqzBA2SRsx7ErDtv6Ayf+XLw9LIOqmpBuHFCVwyJo2EtzGWMB9fYRFBoWWQLxmNmCpenNiH0RxyeS41w==", + "dev": true, + "requires": { + "eslint-restricted-globals": "^0.1.1" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + } + }, + "eslint-module-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", + "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", + "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.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.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "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" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "optional": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^2.1.0" + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "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", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "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", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + } + } + }, + "express-list-routes": { + "version": "0.1.4", + "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" + }, + "dependencies": { + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + } + } + }, + "express-request-id": { + "version": "1.4.1", + "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" + } + }, + "express-sanitizer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/express-sanitizer/-/express-sanitizer-1.0.5.tgz", + "integrity": "sha512-48/Tf1DZ7JklRVTcXQLHAxhq4GNJTuHq2jjIYhyTmu0Bw+X06YPDD/e/tdn1QLYk706xw4N8JFxtjslRrDGb8g==", + "requires": { + "sanitizer": "0.1.3", + "underscore": "1.8.3" + } + }, + "express-validation": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/express-validation/-/express-validation-0.6.0.tgz", + "integrity": "sha1-DXf0r8flixIBat7FmzJb7v2dwmg=", + "requires": { + "lodash": "^4.9.0" + } + }, + "ext": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.2.0.tgz", + "integrity": "sha512-0ccUQK/9e3NreLFg6K6np8aPyRgwycx+oFGtfx1dSp7Wj00Ozw9r05FgBRlzjf2XBM7LAzwgLyDscRrtSU91hA==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true, + "optional": true + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "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" + } + } + } + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "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" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "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" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "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" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "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" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "optional": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.5.1", + "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" + } + }, + "formatio": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", + "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", + "dev": true, + "requires": { + "samsam": "~1.1" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "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.1", + "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": "4.1.1", + "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.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "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.3", + "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.1", + "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.3", + "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.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.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" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "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.0", + "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.6.3", + "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.0", + "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.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "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.0.3", + "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", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "^1.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "optional": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "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" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "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" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", + "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "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" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hashring": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/hashring/-/hashring-3.2.0.tgz", + "integrity": "sha1-/aTv3oqiLNuX+x0qZeiEAeHBRM4=", + "requires": { + "connection-parse": "0.0.x", + "simple-lru-cache": "0.0.x" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true + }, + "http-aws-es": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/http-aws-es/-/http-aws-es-4.0.0.tgz", + "integrity": "sha512-5OJVj9/JSNOVFgIOnBK+9fwDePd35PF1odskYjp/aqstuurZy1XdmHoDP+wPE5LH9Pe/TasIJyARyH7aJnLh/A==" + }, + "http-errors": { + "version": "1.7.2", + "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", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "idtoken-verifier": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-1.4.1.tgz", + "integrity": "sha512-BoJc00Gj37hrNlx7NYmd8uJFvvC9/FiWDKugDluP4JmgOGT/AfNlPfnRmi9fHEEqSatnIIr3WTyf0dlhHfSHnA==", + "requires": { + "base64-js": "^1.2.0", + "crypto-js": "^3.1.9-1", + "jsbn": "^0.1.0", + "promise-polyfill": "^8.1.3", + "unfetch": "^4.1.0", + "url-join": "^1.1.0" + }, + "dependencies": { + "url-join": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", + "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=" + } + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "^1.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" + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "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" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true, + "optional": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "optional": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "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 + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "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-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", + "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" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true, + "optional": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true, + "optional": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isemail": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.2.1.tgz", + "integrity": "sha1-A1PT2aYpUQgMJiwqoKQrjqjp4qY=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "optional": true, + "requires": { + "isarray": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + } + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "1.1.0-alpha.1", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-1.1.0-alpha.1.tgz", + "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" + } + }, + "istanbul-api": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", + "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" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + } + } + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", + "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", + "dev": true, + "requires": { + "append-transform": "^0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "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" + } + }, + "istanbul-lib-report": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", + "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" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", + "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" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "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" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "istanbul-reports": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", + "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", + "dev": true, + "requires": { + "handlebars": "^4.0.3" + } + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "joi": { + "version": "8.4.2", + "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" + } + }, + "join-component": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz", + "integrity": "sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU=" + }, + "js-beautify": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.2.tgz", + "integrity": "sha512-ZtBYyNUYJIsBWERnQP0rPN9KjkrDfJcMjuVGcvXOUJrD1zmOGwhRwQ4msG+HJ+Ni/FA7+sRQEMYVzdTQDvnzvQ==", + "dev": true, + "requires": { + "config-chain": "^1.1.12", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "mkdirp": "~0.5.1", + "nopt": "~4.0.1" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "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" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, + "js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsonwebtoken": { + "version": "8.5.1", + "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" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jwks-rsa": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.6.0.tgz", + "integrity": "sha512-gLhpd7Ka7Jy8ofm9OVj0PFPtSdx3+W2dncF3UCA1wDTAbvfiB1GhHbbyQlz8bqLF5+rge7pgD/DALRfgZi8Fgg==", + "requires": { + "@types/express-jwt": "0.0.42", + "debug": "^2.6.9", + "jsonwebtoken": "^8.5.1", + "limiter": "^1.1.4", + "lru-memoizer": "^1.12.0", + "ms": "^2.1.1", + "request": "^2.88.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jws": { + "version": "3.2.2", + "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" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } + } + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-ass": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.3.0.tgz", + "integrity": "sha1-fQ0U7vPslwLG8wxg6oHxqNP5APs=", + "dev": true + }, + "le_node": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/le_node/-/le_node-1.8.0.tgz", + "integrity": "sha512-NXzjxBskZ4QawTNwlGdRG05jYU0LhV2nxxmP3x7sRMHyROV0jPdyyikO9at+uYrWX3VFt0Y/am11oKITedx0iw==", + "requires": { + "babel-runtime": "6.6.1", + "codependency": "0.1.4", + "json-stringify-safe": "5.0.1", + "lodash": "4.17.11", + "reconnect-core": "1.3.0", + "semver": "5.1.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "semver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", + "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=" + } + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "libpq": { + "version": "1.8.9", + "resolved": "https://registry.npmjs.org/libpq/-/libpq-1.8.9.tgz", + "integrity": "sha512-herU0STiW3+/XBoYRycKKf49O9hBKK0JbdC2QmvdC5pyCSu8prb9idpn5bUSbxj8XwcEsWPWWWwTDZE9ZTwJ7g==", + "requires": { + "bindings": "1.5.0", + "nan": "^2.14.0" + } + }, + "limiter": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.4.tgz", + "integrity": "sha512-XCpr5bElgDI65vVgstP8TWjv6/QKWm9GU5UG0Pr5sLQ3QLo8NVKsioe+Jed5/3vFOe3IQuqE7DKwTvKQkjTHvg==" + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "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" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/lock/-/lock-0.1.4.tgz", + "integrity": "sha1-/sfervF+fDoKVeHaBCgD4l2RdF0=" + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "lolex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", + "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", + "dev": true + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "lru-memoizer": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-1.13.0.tgz", + "integrity": "sha512-q0wMolfI7yimhZ36kBAfMLOIuDBpRkieN9do0YPjSzCaiy6r73s8wOEq7Ue/B95VSRbXzfnOr1O1QdJc5UIqaw==", + "requires": { + "lock": "~0.1.2", + "lodash": "^4.17.4", + "lru-cache": "~4.0.0", + "very-fast-args": "^1.1.0" + } + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "~0.10.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "dev": true, + "optional": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "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" + } + }, + "memwatch-next": { + "version": "0.3.0", + "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" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "method-override": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-2.3.10.tgz", + "integrity": "sha1-49r41d7hDdLc59SuiNYrvud0drQ=", + "requires": { + "debug": "2.6.9", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "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" + } + }, + "millisecond": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/millisecond/-/millisecond-0.1.2.tgz", + "integrity": "sha1-bMWtOGJByrjniv+WT4cCjuyS2sU=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mocha": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz", + "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.0", + "yargs-parser": "13.1.1", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "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" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "moment-timezone": { + "version": "0.5.27", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz", + "integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==", + "requires": { + "moment": ">= 2.9.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "murmur-hash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmur-hash-js/-/murmur-hash-js-1.0.0.tgz", + "integrity": "sha1-UEEEkmnJZjPIZjhpYLL0KJ515bA=" + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + } + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "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" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nice-simple-logger": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nice-simple-logger/-/nice-simple-logger-1.0.1.tgz", + "integrity": "sha1-D55khSe+e+PkmrdvqMjAmK+VG/Y=", + "requires": { + "lodash": "^4.3.0" + } + }, + "no-kafka": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/no-kafka/-/no-kafka-3.4.3.tgz", + "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" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + } + } + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "nodemon": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.4.tgz", + "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" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "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" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "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" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "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" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "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" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "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" + } + }, + "normalize-path": { + "version": "2.1.1", + "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" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "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" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "optional": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optimist": { + "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" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "output-file-sync": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", + "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.4", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "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" + } + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "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" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pg": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.12.1.tgz", + "integrity": "sha512-l1UuyfEvoswYfcUe6k+JaxiN+5vkOgYcVSbSuw3FvdLqDbaoa2RJo1zfJKfPsSYPFVERd4GHvX3s2PjG1asSDA==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "0.1.3", + "pg-pool": "^2.0.4", + "pg-types": "^2.1.0", + "pgpass": "1.x", + "semver": "4.3.2" + }, + "dependencies": { + "semver": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" + } + } + }, + "pg-connection-string": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", + "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-native": { + "version": "3.0.0", + "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", + "readable-stream": "1.0.31" + }, + "dependencies": { + "pg-types": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz", + "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": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz", + "integrity": "sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==" + }, + "readable-stream": { + "version": "1.0.31", + "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", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "pg-pool": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.7.tgz", + "integrity": "sha512-UiJyO5B9zZpu32GSlP0tXy8J2NsJ9EFGFfz5v6PSbdz/1hBLX1rNiiy5+mAm5iJJYwfCv4A0EBcQLGWwjbpzZw==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "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" + } + }, + "pgpass": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", + "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", + "requires": { + "split": "^1.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", + "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==" + }, + "postgres-interval": { + "version": "1.2.0", + "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" + } + }, + "precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true, + "optional": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true + }, + "protocol-buffers-schema": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.3.2.tgz", + "integrity": "sha512-Xdayp8sB/mU+sUV4G7ws8xtYMGdQnxbeIfLjyO9TZZRJdztBGhlmbI5x1qcY4TG5hBkIKGnc28i7nXxaugu88w==" + }, + "proxy-addr": { + "version": "2.0.5", + "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", + "ipaddr.js": "1.9.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "psl": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", + "dev": true + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "qs": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "optional": true + } + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "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" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "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", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "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" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "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" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "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" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "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" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "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" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + } + }, + "really-need": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/really-need/-/really-need-1.9.2.tgz", + "integrity": "sha1-Amp+uwUJ9T89/afJP+uO9TFM3TE=", + "dev": true, + "requires": { + "check-more-types": "2.3.0", + "lazy-ass": "1.3.0" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "reconnect-core": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reconnect-core/-/reconnect-core-1.3.0.tgz", + "integrity": "sha1-+65SkZp4d9hE4yRtAaLyZwHIM8g=", + "requires": { + "backoff": "~2.5.0" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "optional": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "remove-trailing-slash": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.0.tgz", + "integrity": "sha1-FJjl3wmEwn5Jt26/Boh8otARUNI=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "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" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "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" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry-as-promised": { + "version": "3.2.0", + "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" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "^6.0.1" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "^1.3.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "optional": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "samsam": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", + "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", + "dev": true + }, + "sanitizer": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/sanitizer/-/sanitizer-0.1.3.tgz", + "integrity": "sha1-1PCvdHXZp7ryqeWmEXGLqheKOeE=" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "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", + "fresh": "0.5.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" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "sequelize": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.2.tgz", + "integrity": "sha512-MEqJ9NwQi4oy/ylLb2WkfPmhki/BOXC/gJfc8uWUUTETcpLwD1y/5bI1kqVh+qWcECHNsE9G4lmhj5hFbsxqvA==", + "requires": { + "bluebird": "^3.5.0", + "cls-bluebird": "^2.1.0", + "debug": "^4.1.1", + "dottie": "^2.0.0", + "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" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "sequelize-cli": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.5.1.tgz", + "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" + } + }, + "sequelize-pool": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", + "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==" + }, + "serve-static": { + "version": "1.14.1", + "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", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "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" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "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" + } + } + } + }, + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-lru-cache": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz", + "integrity": "sha1-1ZzDoZPBpdAyD4Tucy9uRxPlEd0=" + }, + "sinon": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", + "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", + "dev": true, + "requires": { + "formatio": "1.1.1", + "lolex": "1.3.2", + "samsam": "1.1.2", + "util": ">=0.10.3 <1" + } + }, + "sinon-chai": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.14.0.tgz", + "integrity": "sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "sleep": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/sleep/-/sleep-5.2.4.tgz", + "integrity": "sha512-SoltvxayTifWOgOGD6CTh+djcp5TaOa/zdbaA38wEH1ahF2azmiLOh8CPt6ExHf0pAJAsA9OCHTS7zK24Ym4yA==", + "dev": true, + "requires": { + "nan": ">=2.12.1" + } + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "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" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "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" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "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" + } + }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "requires": { + "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" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "superagent-retry": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/superagent-retry/-/superagent-retry-0.6.0.tgz", + "integrity": "sha1-5Js1ypbA47HQ4/SWBRNt8OCgKLc=" + }, + "supertest": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", + "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "swagger-ui-dist": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.24.2.tgz", + "integrity": "sha512-Nhx9hODibHEa53ErTrguM/N0XaEBcQeKkTlfgJvRwMo/CrJI6ncy8xKOh3meSqQj+oVqz2nhWjMBBudJsRYz5g==" + }, + "swagger-ui-express": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.2.tgz", + "integrity": "sha512-bVT16qj6WdNlEKFkSLOoTeGuqEm2lfOFRq6mVHAx+viA/ikORE+n4CS3WpVcYmQzM4HE6+DUFgAWcMRBJNpjcw==", + "requires": { + "swagger-ui-dist": "^3.18.1" + } + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "^4.7.0", + "ajv-keywords": "^1.0.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", + "slice-ansi": "0.0.4", + "string-width": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "tc-core-library-js": { + "version": "github: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" + } + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "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" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "topo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", + "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", + "requires": { + "hoek": "4.x.x" + } + }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, + "tough-cookie": { + "version": "2.4.3", + "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" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz", + "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.3", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "umzug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.2.0.tgz", + "integrity": "sha512-xZLW76ax70pND9bx3wqwb8zqkFGzZIK8dIHD9WdNy/CrNfjWcwQgQkGCuUqcuwEBvUm+g07z+qWvY+pxDmMEEw==", + "dev": true, + "requires": { + "babel-runtime": "^6.23.0", + "bluebird": "^3.5.3" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + } + } + }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "unfetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz", + "integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "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" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "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" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, + "url-parse": { + "version": "1.4.7", + "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" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "urlencode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/urlencode/-/urlencode-1.1.0.tgz", + "integrity": "sha1-HyuibwE8hfATP3o61v8nMK33y7c=", + "requires": { + "iconv-lite": "~0.4.11" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "util": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.1.tgz", + "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" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "^1.1.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "very-fast-args": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/very-fast-args/-/very-fast-args-1.1.0.tgz", + "integrity": "sha1-4W0dH6+KbllqJGQh/ZCneWPQs5Y=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "winchan": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.2.2.tgz", + "integrity": "sha512-pvN+IFAbRP74n/6mc6phNyCH8oVkzXsto4KCHPJ2AScniAnA1AmeLI03I2BzjePpaClGSI4GUMowzsD3qz5PRQ==" + }, + "wkx": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", + "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==", + "requires": { + "@types/node": "*" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "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" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "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" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "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" + } + }, + "wrr-pool": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/wrr-pool/-/wrr-pool-1.1.4.tgz", + "integrity": "sha512-+lEdj42HlYqmzhvkZrx6xEymj0wzPBxqr7U1Xh9IWikMzOge03JSQT9YzTGq54SkOh/noViq32UejADZVzrgAg==", + "requires": { + "lodash": "^4.17.11" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, + "xml2js": { + "version": "0.4.22", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.22.tgz", + "integrity": "sha512-MWTbxAQqclRSTnehWWe5nMKzI3VmJ8ltiJEco8akcC6j3miOhjjfzKum5sId+CWhfxdOs/1xauYr8/ZDBtQiRw==", + "requires": { + "sax": ">=0.6.0", + "util.promisify": "~1.0.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yamljs": { + "version": "0.3.0", + "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" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "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" + } + } + } + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "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" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "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" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } +} From d100987a002c3891608f6b08f564f69b8d4c303b Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 11 Nov 2019 12:39:48 +0800 Subject: [PATCH 54/88] fix: removed response wrapper in the rest of endpoints Some endpoints hasn't been updated to follow V5 standard and still sent additional metadata in responses. --- src/routes/admin/project-create-index.js | 2 +- src/routes/admin/project-delete-index.js | 2 +- src/routes/admin/project-index-create.js | 4 ++-- src/routes/admin/project-index-delete.js | 2 +- src/routes/projectReports/getReport.js | 2 +- src/routes/projectReports/mock.js | 5 ++--- src/routes/projects/update.js | 2 +- src/routes/projects/update.spec.js | 26 ++++++++++++------------ 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/routes/admin/project-create-index.js b/src/routes/admin/project-create-index.js index d4a00d4b..c0a76d7b 100644 --- a/src/routes/admin/project-create-index.js +++ b/src/routes/admin/project-create-index.js @@ -362,6 +362,6 @@ module.exports = [ const esClient = util.getElasticSearchClient(); esClient.indices.create(getRequestBody(indexName, docType)); - res.status(200).json(util.wrapResponse(req.id, { message: 'Create index request successfully submitted' })); + res.status(200).json({ message: 'Create index request successfully submitted' }); }, ]; diff --git a/src/routes/admin/project-delete-index.js b/src/routes/admin/project-delete-index.js index eb72ccc6..1a172e7f 100644 --- a/src/routes/admin/project-delete-index.js +++ b/src/routes/admin/project-delete-index.js @@ -40,6 +40,6 @@ module.exports = [ // we would want to ignore no such index error ignore: [404], }); - return res.status(200).json(util.wrapResponse(req.id, { message: 'Delete index request successfully submitted' })); + return res.status(200).json({ message: 'Delete index request successfully submitted' }); }, ]; diff --git a/src/routes/admin/project-index-create.js b/src/routes/admin/project-index-create.js index a6f8dfb8f9..a08bf87d 100644 --- a/src/routes/admin/project-index-create.js +++ b/src/routes/admin/project-index-create.js @@ -108,9 +108,9 @@ module.exports = [ logger.trace('body[length-1]', body[body.length - 1]); } - res.status(200).json(util.wrapResponse(req.id, { + res.status(200).json({ message: `Reindex request successfully submitted for ${body.length / 2} projects`, - })); + }); // bulk index eClient.bulk({ body, diff --git a/src/routes/admin/project-index-delete.js b/src/routes/admin/project-index-delete.js index 54f1c403..53ec9cf0 100644 --- a/src/routes/admin/project-index-delete.js +++ b/src/routes/admin/project-index-delete.js @@ -78,7 +78,7 @@ module.exports = [ .catch((error) => { logger.error(`Error in deleting indexes for project (projectId: ${projectIdStart}-${projectIdEnd})`, error); }); - res.status(200).json(util.wrapResponse(req.id, { message: 'Delete index request successfully submitted' })); + res.status(200).json({ message: 'Delete index request successfully submitted' }); }); }) .catch(err => next(err)); diff --git a/src/routes/projectReports/getReport.js b/src/routes/projectReports/getReport.js index aa4253c3..fe842fd6 100644 --- a/src/routes/projectReports/getReport.js +++ b/src/routes/projectReports/getReport.js @@ -21,7 +21,7 @@ module.exports = [ req.log.info('using mock'); // using mock return mock(projectId, reportName, req, res); - // res.status(200).json(util.wrapResponse(req.id, project)); + // res.status(200).json(project); } const lookApi = new LookApi(req.log); diff --git a/src/routes/projectReports/mock.js b/src/routes/projectReports/mock.js index 33e6787d..9d52e5dc 100644 --- a/src/routes/projectReports/mock.js +++ b/src/routes/projectReports/mock.js @@ -1,5 +1,4 @@ import _ from 'lodash'; -import util from '../../util'; const summaryJson = require('./mockFiles/summary.json'); let projectBudgetJson = require('./mockFiles/projectBudget.json'); @@ -11,12 +10,12 @@ module.exports = (projectId, reportName, req, res) => { switch (reportName) { case 'summary': - res.status(200).json(util.wrapResponse(req.id, summaryJson)); + res.status(200).json(summaryJson); break; case 'projectBudget': { const augmentProjectId = pb => _.assign(pb, { 'project_stream.tc_connect_project_id': projectId }); projectBudgetJson = _.map(projectBudgetJson, augmentProjectId); - res.status(200).json(util.wrapResponse(req.id, projectBudgetJson)); + res.status(200).json(projectBudgetJson); break; } default: diff --git a/src/routes/projects/update.js b/src/routes/projects/update.js index 6418bf18..dfb4264c 100644 --- a/src/routes/projects/update.js +++ b/src/routes/projects/update.js @@ -292,7 +292,7 @@ module.exports = [ .then((attachments) => { // make sure we only send response after transaction is committed project.attachments = attachments; - res.json(util.wrapResponse(req.id, project)); + res.json(project); }) .catch(err => next(err)); }, diff --git a/src/routes/projects/update.spec.js b/src/routes/projects/update.spec.js index 91ff4e93..e198659e 100644 --- a/src/routes/projects/update.spec.js +++ b/src/routes/projects/update.spec.js @@ -203,7 +203,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.equal('updatedProject name'); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); @@ -232,7 +232,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.equal('updatedProject name'); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); @@ -278,7 +278,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.equal('updatedProject name'); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); @@ -353,7 +353,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.equal('updatedProject name'); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); @@ -404,7 +404,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.equal('updatedProject name'); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); @@ -456,7 +456,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.equal('updatedProject name'); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); @@ -532,7 +532,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.equal('updatedProject name'); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); @@ -568,7 +568,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.billingAccountId.should.equal(123); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); @@ -595,7 +595,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.billingAccountId.should.equal(1); resJson.billingAccountId.should.equal(1); @@ -620,7 +620,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.billingAccountId.should.equal(1); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); @@ -649,7 +649,7 @@ describe('Project', () => { if (err) { done(err); } else { - let resJson = res.body.result.content; + let resJson = res.body; should.exist(resJson); resJson.bookmarks.should.have.lengthOf(1); resJson.bookmarks[0].title.should.be.eql('title1'); @@ -669,7 +669,7 @@ describe('Project', () => { if (error) { done(error); } else { - resJson = resp.body.result.content; + resJson = resp.body; should.exist(resJson); should.not.exist(resJson.bookmarks); server.services.pubsub.publish.calledWith('project.updated').should.be.true; @@ -708,7 +708,7 @@ describe('Project', () => { if (err) { done(err); } else { - const resJson = res.body.result.content; + const resJson = res.body; should.exist(resJson); resJson.name.should.equal('updatedProject name'); resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00'); From 62fa47f2e5ae54a8dde9602de291d32a1e35def5 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 11 Nov 2019 12:40:54 +0800 Subject: [PATCH 55/88] fix: request list of project from DB Only users with the invitation in 'pending' status can see projects in the list. --- src/models/project.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/models/project.js b/src/models/project.js index 6cfb9d03..c09accb7 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -1,7 +1,7 @@ /* eslint-disable valid-jsdoc */ import _ from 'lodash'; -import { PROJECT_STATUS } from '../constants'; +import { PROJECT_STATUS, INVITE_STATUS } from '../constants'; module.exports = function defineProject(sequelize, DataTypes) { const Project = sequelize.define('Project', { @@ -96,7 +96,9 @@ module.exports = function defineProject(sequelize, DataTypes) { Project.searchText = (parameters, log) => { // special handling for keyword filter let query = '1=1 '; - const replacements = {}; + const replacements = { + INVITE_STATUS_PENDING: INVITE_STATUS.PENDING, + }; if (_.has(parameters.filters, 'id')) { if (_.isArray(parameters.filters.id)) { if (parameters.filters.id.length === 0) { @@ -138,9 +140,13 @@ module.exports = function defineProject(sequelize, DataTypes) { let joinQuery = ''; if (_.has(parameters.filters, 'userId') || _.has(parameters.filters, 'email')) { - query += ` AND (members."userId" = :userId - OR invites."userId" = :userId - OR invites."email" = :email) GROUP BY projects.id`; + query += ` AND ( + members."userId" = :userId + OR ( + invites.status = :INVITE_STATUS_PENDING AND + (invites."userId" = :userId OR invites."email" = :email) + ) + ) GROUP BY projects.id`; joinQuery = `LEFT OUTER JOIN project_members AS members ON projects.id = members."projectId" LEFT OUTER JOIN project_member_invites AS invites ON projects.id = invites."projectId"`; From c51d00ed57d534b5c520082f6d7c9a3b62f8b083 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 11 Nov 2019 12:46:43 +0800 Subject: [PATCH 56/88] fix: request list of project from DB (for deleted members) Only members which are not deleted can see the project in the list. --- src/models/project.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/project.js b/src/models/project.js index c09accb7..b111d2d1 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -141,7 +141,7 @@ module.exports = function defineProject(sequelize, DataTypes) { let joinQuery = ''; if (_.has(parameters.filters, 'userId') || _.has(parameters.filters, 'email')) { query += ` AND ( - members."userId" = :userId + members."userId" = :userId AND members.deletedAt IS NULL OR ( invites.status = :INVITE_STATUS_PENDING AND (invites."userId" = :userId OR invites."email" = :email) From 1dfaeb1b2078fc8232009a8dd33c7c763a3a3ca3 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 11 Nov 2019 13:07:47 +0800 Subject: [PATCH 57/88] fix: request list of project from DB (for deleted members), part 2 --- src/models/project.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/project.js b/src/models/project.js index b111d2d1..fcc3083d 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -141,7 +141,7 @@ module.exports = function defineProject(sequelize, DataTypes) { let joinQuery = ''; if (_.has(parameters.filters, 'userId') || _.has(parameters.filters, 'email')) { query += ` AND ( - members."userId" = :userId AND members.deletedAt IS NULL + members."userId" = :userId AND members."deletedAt" IS NULL OR ( invites.status = :INVITE_STATUS_PENDING AND (invites."userId" = :userId OR invites."email" = :email) From 077d4a2e1ca6899fce42f8a9f5003b4f1296c9cb Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 11 Nov 2019 14:08:40 +0800 Subject: [PATCH 58/88] fix: attachment create Should send request to File Service API using v3 API fomat. --- src/routes/attachments/create.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index f46a4a97..a58a5abe 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -71,9 +71,11 @@ module.exports = [ // get pre-signed Url req.log.debug('requesting presigned Url'); httpClient.post(`${fileServiceUrl}uploadurl/`, { - filePath, - contentType: data.contentType, - isPublic: false, + param: { + filePath, + contentType: data.contentType, + isPublic: false, + } }).then((resp) => { req.log.debug('Presigned Url resp: ', JSON.stringify(resp.data, null, 2)); if (resp.status !== 200 || resp.data.result.status !== 200) { From 807be5de3745b285b55105767984b87794517b1c Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 11 Nov 2019 14:13:00 +0800 Subject: [PATCH 59/88] fix: lint --- src/routes/attachments/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index a58a5abe..6b36ca68 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -75,7 +75,7 @@ module.exports = [ filePath, contentType: data.contentType, isPublic: false, - } + }, }).then((resp) => { req.log.debug('Presigned Url resp: ', JSON.stringify(resp.data, null, 2)); if (resp.status !== 200 || resp.data.result.status !== 200) { From 6d5f9a6bb8e19739c027e5b9f917e356d12e27f5 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 11 Nov 2019 18:09:14 +0800 Subject: [PATCH 60/88] feat: improved admin endpoints to create and delete ES indexes Now endpoints support creating/deleting any indexes. Also, done some refactoring to reuse code and avoid code duplication which may lead to inconsistency issues. --- migrations/elasticsearch_sync.js | 757 +---------------------- src/routes/admin/es-create-index.js | 49 ++ src/routes/admin/es-delete-index.js | 39 ++ src/routes/admin/project-create-index.js | 367 ----------- src/routes/admin/project-delete-index.js | 45 -- src/routes/index.js | 8 +- src/utils/es-config.js | 719 +++++++++++++++++++++ src/utils/es.js | 29 + 8 files changed, 851 insertions(+), 1162 deletions(-) create mode 100644 src/routes/admin/es-create-index.js create mode 100644 src/routes/admin/es-delete-index.js delete mode 100644 src/routes/admin/project-create-index.js delete mode 100644 src/routes/admin/project-delete-index.js create mode 100644 src/utils/es-config.js diff --git a/migrations/elasticsearch_sync.js b/migrations/elasticsearch_sync.js index 88ec0b73..cc20a024 100644 --- a/migrations/elasticsearch_sync.js +++ b/migrations/elasticsearch_sync.js @@ -13,754 +13,15 @@ import config from 'config'; import util from '../src/util'; +import esUtils from '../src/utils/es'; +import { INDEX_TO_DOC_TYPE } from '../src/utils/es-config'; const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); -const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName'); -const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType'); const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); -const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); -const allowedIndexes = [ES_PROJECT_INDEX, ES_TIMELINE_INDEX, ES_METADATA_INDEX]; - -// create new elasticsearch client -// the client modifies the config object, so always passed the cloned object -const esClient = util.getElasticSearchClient(); - -/** - * Get the request body for the specified index name - * @private - * - * @param {String} indexName the index name - * @return {Object} the request body for the specified index name - */ -function getRequestBody(indexName) { - let result; - const projectMapping = { - _all: { enabled: false }, - properties: { - actualPrice: { - type: 'double', - }, - attachments: { - type: 'nested', - properties: { - category: { - type: 'string', - index: 'not_analyzed', - }, - contentType: { - type: 'string', - index: 'not_analyzed', - }, - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - description: { - type: 'string', - }, - filePath: { - type: 'string', - }, - id: { - type: 'long', - }, - projectId: { - type: 'long', - }, - size: { - type: 'double', - }, - title: { - type: 'string', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - billingAccountId: { - type: 'long', - }, - bookmarks: { - type: 'nested', - properties: { - address: { - type: 'string', - }, - title: { - type: 'string', - }, - }, - }, - cancelReason: { - type: 'string', - }, - challengeEligibility: { - type: 'nested', - properties: { - groups: { - type: 'long', - }, - role: { - type: 'string', - index: 'not_analyzed', - }, - users: { - type: 'long', - }, - }, - }, - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - description: { - type: 'string', - }, - details: { - type: 'nested', - properties: { - TBD_features: { - type: 'nested', - properties: { - description: { - type: 'string', - }, - id: { - type: 'integer', - }, - isCustom: { - type: 'boolean', - }, - title: { - type: 'string', - }, - }, - }, - TBD_usageDescription: { - type: 'string', - }, - appDefinition: { - properties: { - goal: { - properties: { - value: { - type: 'string', - }, - }, - }, - primaryTarget: { - type: 'string', - }, - users: { - properties: { - value: { - type: 'string', - }, - }, - }, - }, - }, - hideDiscussions: { - type: 'boolean', - }, - products: { - type: 'string', - }, - summary: { - type: 'string', - }, - utm: { - type: 'nested', - properties: { - code: { - type: 'string', - }, - }, - }, - }, - }, - directProjectId: { - type: 'long', - }, - estimatedPrice: { - type: 'double', - }, - external: { - properties: { - data: { - type: 'string', - }, - id: { - type: 'string', - index: 'not_analyzed', - }, - type: { - type: 'string', - index: 'not_analyzed', - }, - }, - }, - id: { - type: 'long', - }, - members: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - email: { - type: 'string', - index: 'not_analyzed', - }, - firstName: { - type: 'string', - }, - handle: { - type: 'string', - index: 'not_analyzed', - }, - id: { - type: 'long', - }, - isPrimary: { - type: 'boolean', - }, - lastName: { - type: 'string', - }, - projectId: { - type: 'long', - }, - role: { - type: 'string', - index: 'not_analyzed', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - userId: { - type: 'long', - }, - }, - }, - invites: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - email: { - type: 'string', - index: 'not_analyzed', - }, - id: { - type: 'long', - }, - role: { - type: 'string', - index: 'not_analyzed', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - userId: { - type: 'long', - }, - projectId: { - type: 'long', - }, - }, - }, - name: { - type: 'string', - }, - status: { - type: 'string', - index: 'not_analyzed', - }, - terms: { - type: 'integer', - }, - type: { - type: 'string', - index: 'not_analyzed', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - lastActivityAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - lastActivityUserId: { - type: 'string', - }, - utm: { - properties: { - campaign: { - type: 'string', - }, - medium: { - type: 'string', - }, - source: { - type: 'string', - }, - }, - }, - phases: { - type: 'nested', - dynamic: true, - }, - }, - }; - - // form config can be present inside 3 models, so we reuse it - const formConfig = { - type: 'object', - properties: { - sections: { - type: 'nested', - properties: { - subSections: { - type: 'nested', - properties: { - questions: { - type: 'nested', - properties: { - options: { - type: 'nested', - properties: { - value: { - type: 'string', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }; - - const metadataMapping = { - _all: { enabled: false }, - properties: { - projectTemplates: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - key: { - type: 'string', - index: 'not_analyzed', - }, - category: { - type: 'string', - index: 'not_analyzed', - }, - name: { - type: 'string', - }, - id: { - type: 'long', - }, - scope: formConfig, - form: { - type: 'object', - }, - priceConfig: { - type: 'object', - }, - planConfig: { - type: 'object', - }, - phases: { - type: 'object', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - forms: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - key: { - type: 'string', - index: 'not_analyzed', - }, - config: formConfig, - version: { - type: 'integer', - }, - revision: { - type: 'integer', - }, - id: { - type: 'long', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - - planConfigs: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - key: { - type: 'string', - index: 'not_analyzed', - }, - version: { - type: 'integer', - }, - revision: { - type: 'integer', - }, - id: { - type: 'long', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - - priceConfigs: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - key: { - type: 'string', - index: 'not_analyzed', - }, - version: { - type: 'integer', - }, - revision: { - type: 'integer', - }, - id: { - type: 'long', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - - orgConfigs: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - orgId: { - type: 'string', - index: 'not_analyzed', - }, - configName: { - type: 'string', - index: 'not_analyzed', - }, - configValue: { - type: 'string', - }, - id: { - type: 'long', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - - productTemplates: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - name: { - type: 'string', - }, - template: formConfig, - productKey: { - type: 'string', - index: 'not_analyzed', - }, - category: { - type: 'string', - }, - subCategory: { - type: 'string', - index: 'not_analyzed', - }, - id: { - type: 'long', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - - projectTypes: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - displayName: { - type: 'string', - }, - key: { - type: 'string', - index: 'not_analyzed', - }, - id: { - type: 'long', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - - productCategories: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - displayName: { - type: 'string', - }, - key: { - type: 'string', - index: 'not_analyzed', - }, - id: { - type: 'long', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - - buildingBlocks: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - key: { - type: 'string', - index: 'not_analyzed', - }, - id: { - type: 'long', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - - milestoneTemplates: { - type: 'nested', - properties: { - referenceId: { - type: 'long', - }, - reference: { - type: 'string', - index: 'not_analyzed', - }, - id: { - type: 'long', - }, - order: { - type: 'long', - }, - }, - }, - }, - }; - - const timelineMapping = { - _all: { enabled: false }, - properties: { - milestones: { - type: 'nested', - properties: { - id: { - type: 'long', - }, - timelineId: { - type: 'long', - }, - }, - }, - }, - }; - - switch (indexName) { - case ES_PROJECT_INDEX: - result = { - index: indexName, - updateAllTypes: true, - body: { - mappings: { }, - }, - }; - result.body.mappings[ES_PROJECT_TYPE] = projectMapping; - break; - case ES_METADATA_INDEX: - result = { - index: indexName, - updateAllTypes: true, - body: { - mappings: { }, - }, - }; - result.body.mappings[ES_METADATA_TYPE] = metadataMapping; - break; - case ES_TIMELINE_INDEX: - result = { - index: indexName, - updateAllTypes: true, - body: { - mappings: { }, - }, - }; - result.body.mappings[ES_TIMELINE_TYPE] = timelineMapping; - break; - default: - throw new Error(`Invalid index name '${indexName}'`); - } - return result; -} +// all indexes supported by this script +const supportedIndexes = [ES_PROJECT_INDEX, ES_TIMELINE_INDEX, ES_METADATA_INDEX]; /** * Sync elasticsearch indices. @@ -770,10 +31,14 @@ function getRequestBody(indexName) { * @returns {Promise} resolved when sync is complete */ async function sync(indexName) { - if (indexName && allowedIndexes.indexOf(indexName) === -1) { + if (indexName && supportedIndexes.indexOf(indexName) === -1) { throw new Error(`Index "${indexName}" is not supported.`); } - const indexesToSync = indexName ? [indexName] : allowedIndexes; + const indexesToSync = indexName ? [indexName] : supportedIndexes; + + // create new elasticsearch client + // the client modifies the config object, so always passed the cloned object + const esClient = util.getElasticSearchClient(); for (let i = 0; i < indexesToSync.length; i += 1) { const indexToSync = indexesToSync[i]; @@ -781,7 +46,7 @@ async function sync(indexName) { console.log(`Deleting "${indexToSync}" index...`); await esClient.indices.delete({ index: indexToSync, ignore: [404] }); // eslint-disable-line no-await-in-loop console.log(`Creating "${indexToSync}" index...`); - await esClient.indices.create(getRequestBody(indexToSync)); // eslint-disable-line no-await-in-loop + await esClient.indices.create(esUtils.buildCreateIndexRequest(indexToSync, INDEX_TO_DOC_TYPE[indexToSync])); // eslint-disable-line no-await-in-loop } } diff --git a/src/routes/admin/es-create-index.js b/src/routes/admin/es-create-index.js new file mode 100644 index 00000000..1d0e3048 --- /dev/null +++ b/src/routes/admin/es-create-index.js @@ -0,0 +1,49 @@ +/** + * Admin endpoint to create ES index. + * + * Waits until the operation is completed and returns result. + */ +import _ from 'lodash'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; +import esUtils from '../../utils/es'; +import { INDEX_TO_DOC_TYPE } from '../../utils/es-config'; + +const permissions = tcMiddleware.permissions; + +module.exports = [ + permissions('project.admin'), + (req, res, next) => { + try { + const logger = req.log; + logger.debug('Entered Admin#createIndex'); + + const indexName = _.get(req, 'body.indexName'); + if (!indexName) { + const apiErr = new Error('"indexName" is required.'); + apiErr.status = 400; + throw apiErr; + } + + const docType = _.get(req, 'body.param.docType', INDEX_TO_DOC_TYPE[indexName]); + if (!docType) { + const apiErr = new Error('Cannot find "docType" for the index.'); + apiErr.status = 500; + throw apiErr; + } + + logger.debug('indexName', indexName); + logger.debug('docType', docType); + + + const esClient = util.getElasticSearchClient(); + esClient.indices.create(esUtils.buildCreateIndexRequest(indexName, docType)) + .then(() => { + res.status(200).json({ message: 'Index successfully created.' }); + }) + .catch(next); + } catch (err) { + next(err); + } + }, +]; diff --git a/src/routes/admin/es-delete-index.js b/src/routes/admin/es-delete-index.js new file mode 100644 index 00000000..da390253 --- /dev/null +++ b/src/routes/admin/es-delete-index.js @@ -0,0 +1,39 @@ + +/** + * Admin endpoint to delete ES index. + * + * Waits until the operation is completed and returns result. + */ +import _ from 'lodash'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import util from '../../util'; + +const permissions = tcMiddleware.permissions; + +module.exports = [ + permissions('project.admin'), + (req, res, next) => { + try { + const logger = req.log; + logger.debug('Entered Admin#deleteIndex'); + + const indexName = _.get(req, 'body.indexName'); + logger.debug('indexName', indexName); + + if (!indexName) { + const apiErr = new Error('"indexName" is required.'); + apiErr.status = 400; + throw apiErr; + } + + const esClient = util.getElasticSearchClient(); + esClient.indices.delete({ index: indexName }) + .then(() => { + res.status(200).json({ message: 'Index successfully deleted.' }); + }) + .catch(next); + } catch (err) { + next(err); + } + }, +]; diff --git a/src/routes/admin/project-create-index.js b/src/routes/admin/project-create-index.js deleted file mode 100644 index c0a76d7b..00000000 --- a/src/routes/admin/project-create-index.js +++ /dev/null @@ -1,367 +0,0 @@ - -/* globals Promise */ - -import _ from 'lodash'; -import config from 'config'; -import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; - -/** -/** - * API to handle retrieving a single project by id - * - * Permissions: - * Only users that have access to the project can retrieve it. - * - */ - -// var permissions = require('tc-core-library-js').middleware.permissions -const permissions = tcMiddleware.permissions; -const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); -const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); - -/** - * Get the request body for the specified index name - * @private - * - * @param {String} indexName the index name - * @param {String} docType document type - * @return {Object} the request body for the specified index name - */ -function getRequestBody(indexName, docType) { - const projectMapping = { - _all: { enabled: false }, - properties: { - actualPrice: { - type: 'double', - }, - attachments: { - type: 'nested', - properties: { - category: { - type: 'string', - index: 'not_analyzed', - }, - contentType: { - type: 'string', - index: 'not_analyzed', - }, - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - description: { - type: 'string', - }, - filePath: { - type: 'string', - }, - id: { - type: 'long', - }, - projectId: { - type: 'long', - }, - size: { - type: 'double', - }, - title: { - type: 'string', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - }, - }, - billingAccountId: { - type: 'long', - }, - bookmarks: { - type: 'nested', - properties: { - address: { - type: 'string', - }, - title: { - type: 'string', - }, - }, - }, - cancelReason: { - type: 'string', - }, - challengeEligibility: { - type: 'nested', - properties: { - groups: { - type: 'long', - }, - role: { - type: 'string', - index: 'not_analyzed', - }, - users: { - type: 'long', - }, - }, - }, - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - description: { - type: 'string', - }, - details: { - type: 'nested', - properties: { - TBD_features: { - type: 'nested', - properties: { - description: { - type: 'string', - }, - id: { - type: 'integer', - }, - isCustom: { - type: 'boolean', - }, - title: { - type: 'string', - }, - }, - }, - TBD_usageDescription: { - type: 'string', - }, - appDefinition: { - properties: { - goal: { - properties: { - value: { - type: 'string', - }, - }, - }, - primaryTarget: { - type: 'string', - }, - users: { - properties: { - value: { - type: 'string', - }, - }, - }, - }, - }, - hideDiscussions: { - type: 'boolean', - }, - products: { - type: 'string', - }, - summary: { - type: 'string', - }, - utm: { - type: 'nested', - properties: { - code: { - type: 'string', - }, - }, - }, - }, - }, - directProjectId: { - type: 'long', - }, - estimatedPrice: { - type: 'double', - }, - external: { - properties: { - data: { - type: 'string', - }, - id: { - type: 'string', - index: 'not_analyzed', - }, - type: { - type: 'string', - index: 'not_analyzed', - }, - }, - }, - id: { - type: 'long', - }, - members: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - email: { - type: 'string', - index: 'not_analyzed', - }, - firstName: { - type: 'string', - }, - handle: { - type: 'string', - index: 'not_analyzed', - }, - id: { - type: 'long', - }, - isPrimary: { - type: 'boolean', - }, - lastName: { - type: 'string', - }, - projectId: { - type: 'long', - }, - role: { - type: 'string', - index: 'not_analyzed', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - lastActivityAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - lastActivityUserId: { - type: 'string', - }, - userId: { - type: 'long', - }, - }, - }, - invites: { - type: 'nested', - properties: { - createdAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - createdBy: { - type: 'integer', - }, - email: { - type: 'string', - index: 'not_analyzed', - }, - id: { - type: 'long', - }, - role: { - type: 'string', - index: 'not_analyzed', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - userId: { - type: 'long', - }, - }, - }, - name: { - type: 'string', - }, - status: { - type: 'string', - index: 'not_analyzed', - }, - terms: { - type: 'integer', - }, - type: { - type: 'string', - index: 'not_analyzed', - }, - updatedAt: { - type: 'date', - format: 'strict_date_optional_time||epoch_millis', - }, - updatedBy: { - type: 'integer', - }, - utm: { - properties: { - campaign: { - type: 'string', - }, - medium: { - type: 'string', - }, - source: { - type: 'string', - }, - }, - }, - }, - }; - const result = { - index: indexName, - updateAllTypes: true, - body: { - mappings: { }, - }, - }; - result.body.mappings[docType] = projectMapping; - return result; -} - - -module.exports = [ - permissions('project.admin'), - /** - * GET projects/{projectId} - * Get a project by id - */ - (req, res, next) => { // eslint-disable-line no-unused-vars - const logger = req.log; - logger.debug('Entered Admin#createIndex'); - const indexName = _.get(req, 'body.param.indexName', ES_PROJECT_INDEX); - const docType = _.get(req, 'body.param.docType', ES_PROJECT_TYPE); - logger.debug('indexName', indexName); - logger.debug('docType', docType); - - const esClient = util.getElasticSearchClient(); - esClient.indices.create(getRequestBody(indexName, docType)); - res.status(200).json({ message: 'Create index request successfully submitted' }); - }, -]; diff --git a/src/routes/admin/project-delete-index.js b/src/routes/admin/project-delete-index.js deleted file mode 100644 index 1a172e7f..00000000 --- a/src/routes/admin/project-delete-index.js +++ /dev/null @@ -1,45 +0,0 @@ - -/* globals Promise */ - -import _ from 'lodash'; -import { middleware as tcMiddleware } from 'tc-core-library-js'; -import util from '../../util'; - -/** -/** - * API to handle retrieving a single project by id - * - * Permissions: - * Only users that have access to the project can retrieve it. - * - */ - -// var permissions = require('tc-core-library-js').middleware.permissions -const permissions = tcMiddleware.permissions; - -module.exports = [ - permissions('project.admin'), - /** - * GET projects/{projectId} - * Get a project by id - */ - (req, res, next) => { // eslint-disable-line no-unused-vars - const logger = req.log; - logger.debug('Entered Admin#deleteIndex'); - const indexName = _.get(req, 'body.param.indexName'); - logger.debug('indexName', indexName); - if (!indexName) { - const apiErr = new Error('indexName is required'); - apiErr.status = 400; - return Promise.reject(apiErr); - } - - const esClient = util.getElasticSearchClient(); - esClient.indices.delete({ - index: indexName, - // we would want to ignore no such index error - ignore: [404], - }); - return res.status(200).json({ message: 'Delete index request successfully submitted' }); - }, -]; diff --git a/src/routes/index.js b/src/routes/index.js index c57e6651..63c36d0a 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -81,10 +81,10 @@ router.route('/v5/projects') .post(require('./projects/create')) .get(require('./projects/list')); -router.route('/v5/projects/admin/es/project/createIndex') - .post(require('./admin/project-create-index')); -router.route('/v5/projects/admin/es/project/deleteIndex') - .delete(require('./admin/project-delete-index')); +router.route('/v5/projects/admin/es/createIndex') + .post(require('./admin/es-create-index')); +router.route('/v5/projects/admin/es/deleteIndex') + .delete(require('./admin/es-delete-index')); router.route('/v5/projects/admin/es/project/index') .post(require('./admin/project-index-create')); router.route('/v5/projects/admin/es/project/remove') diff --git a/src/utils/es-config.js b/src/utils/es-config.js new file mode 100644 index 00000000..657add4b --- /dev/null +++ b/src/utils/es-config.js @@ -0,0 +1,719 @@ +import config from 'config'; + +const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName'); +const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType'); +const ES_TIMELINE_INDEX = config.get('elasticsearchConfig.timelineIndexName'); +const ES_TIMELINE_TYPE = config.get('elasticsearchConfig.timelineDocType'); +const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); +const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); + +// form config can be present inside 3 models, so we reuse it +const formConfig = { + type: 'object', + properties: { + sections: { + type: 'nested', + properties: { + subSections: { + type: 'nested', + properties: { + questions: { + type: 'nested', + properties: { + options: { + type: 'nested', + properties: { + value: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; + +const MAPPINGS = {}; + +/** + * 'project' index mapping + */ +MAPPINGS[ES_PROJECT_INDEX] = { + _all: { enabled: false }, + properties: { + actualPrice: { + type: 'double', + }, + attachments: { + type: 'nested', + properties: { + category: { + type: 'string', + index: 'not_analyzed', + }, + contentType: { + type: 'string', + index: 'not_analyzed', + }, + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + description: { + type: 'string', + }, + filePath: { + type: 'string', + }, + id: { + type: 'long', + }, + projectId: { + type: 'long', + }, + size: { + type: 'double', + }, + title: { + type: 'string', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + billingAccountId: { + type: 'long', + }, + bookmarks: { + type: 'nested', + properties: { + address: { + type: 'string', + }, + title: { + type: 'string', + }, + }, + }, + cancelReason: { + type: 'string', + }, + challengeEligibility: { + type: 'nested', + properties: { + groups: { + type: 'long', + }, + role: { + type: 'string', + index: 'not_analyzed', + }, + users: { + type: 'long', + }, + }, + }, + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + description: { + type: 'string', + }, + details: { + type: 'nested', + properties: { + TBD_features: { + type: 'nested', + properties: { + description: { + type: 'string', + }, + id: { + type: 'integer', + }, + isCustom: { + type: 'boolean', + }, + title: { + type: 'string', + }, + }, + }, + TBD_usageDescription: { + type: 'string', + }, + appDefinition: { + properties: { + goal: { + properties: { + value: { + type: 'string', + }, + }, + }, + primaryTarget: { + type: 'string', + }, + users: { + properties: { + value: { + type: 'string', + }, + }, + }, + }, + }, + hideDiscussions: { + type: 'boolean', + }, + products: { + type: 'string', + }, + summary: { + type: 'string', + }, + utm: { + type: 'nested', + properties: { + code: { + type: 'string', + }, + }, + }, + }, + }, + directProjectId: { + type: 'long', + }, + estimatedPrice: { + type: 'double', + }, + external: { + properties: { + data: { + type: 'string', + }, + id: { + type: 'string', + index: 'not_analyzed', + }, + type: { + type: 'string', + index: 'not_analyzed', + }, + }, + }, + id: { + type: 'long', + }, + members: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + email: { + type: 'string', + index: 'not_analyzed', + }, + firstName: { + type: 'string', + }, + handle: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + isPrimary: { + type: 'boolean', + }, + lastName: { + type: 'string', + }, + projectId: { + type: 'long', + }, + role: { + type: 'string', + index: 'not_analyzed', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + userId: { + type: 'long', + }, + }, + }, + invites: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + email: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + role: { + type: 'string', + index: 'not_analyzed', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + userId: { + type: 'long', + }, + projectId: { + type: 'long', + }, + }, + }, + name: { + type: 'string', + }, + status: { + type: 'string', + index: 'not_analyzed', + }, + terms: { + type: 'integer', + }, + type: { + type: 'string', + index: 'not_analyzed', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + lastActivityAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + lastActivityUserId: { + type: 'string', + }, + utm: { + properties: { + campaign: { + type: 'string', + }, + medium: { + type: 'string', + }, + source: { + type: 'string', + }, + }, + }, + phases: { + type: 'nested', + dynamic: true, + }, + }, +}; + +/** + * 'timeline' index mapping + */ +MAPPINGS[ES_TIMELINE_INDEX] = { + _all: { enabled: false }, + properties: { + milestones: { + type: 'nested', + properties: { + id: { + type: 'long', + }, + timelineId: { + type: 'long', + }, + }, + }, + }, +}; + +/** + * 'metadata' index mapping + */ +MAPPINGS[ES_METADATA_INDEX] = { + _all: { enabled: false }, + properties: { + projectTemplates: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + category: { + type: 'string', + index: 'not_analyzed', + }, + name: { + type: 'string', + }, + id: { + type: 'long', + }, + scope: formConfig, + form: { + type: 'object', + }, + priceConfig: { + type: 'object', + }, + planConfig: { + type: 'object', + }, + phases: { + type: 'object', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + forms: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + config: formConfig, + version: { + type: 'integer', + }, + revision: { + type: 'integer', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + planConfigs: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + version: { + type: 'integer', + }, + revision: { + type: 'integer', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + priceConfigs: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + version: { + type: 'integer', + }, + revision: { + type: 'integer', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + orgConfigs: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + orgId: { + type: 'string', + index: 'not_analyzed', + }, + configName: { + type: 'string', + index: 'not_analyzed', + }, + configValue: { + type: 'string', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + productTemplates: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + name: { + type: 'string', + }, + template: formConfig, + productKey: { + type: 'string', + index: 'not_analyzed', + }, + category: { + type: 'string', + }, + subCategory: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + projectTypes: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + displayName: { + type: 'string', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + productCategories: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + displayName: { + type: 'string', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + buildingBlocks: { + type: 'nested', + properties: { + createdAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + createdBy: { + type: 'integer', + }, + key: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + updatedAt: { + type: 'date', + format: 'strict_date_optional_time||epoch_millis', + }, + updatedBy: { + type: 'integer', + }, + }, + }, + + milestoneTemplates: { + type: 'nested', + properties: { + referenceId: { + type: 'long', + }, + reference: { + type: 'string', + index: 'not_analyzed', + }, + id: { + type: 'long', + }, + order: { + type: 'long', + }, + }, + }, + }, +}; + +// mapping between indexes and their docTypes +const INDEX_TO_DOC_TYPE = {}; +INDEX_TO_DOC_TYPE[ES_PROJECT_INDEX] = ES_PROJECT_TYPE; +INDEX_TO_DOC_TYPE[ES_TIMELINE_INDEX] = ES_TIMELINE_TYPE; +INDEX_TO_DOC_TYPE[ES_METADATA_INDEX] = ES_METADATA_TYPE; + +module.exports = { + MAPPINGS, + INDEX_TO_DOC_TYPE, +}; diff --git a/src/utils/es.js b/src/utils/es.js index a56dab6e..c25e7395 100644 --- a/src/utils/es.js +++ b/src/utils/es.js @@ -5,6 +5,7 @@ import _ from 'lodash'; import config from 'config'; import util from '../util'; import models from '../models'; +import { MAPPINGS } from './es-config'; const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); const ES_METADATA_TYPE = config.get('elasticsearchConfig.metadataDocType'); @@ -70,6 +71,34 @@ async function indexMetadata() { }); } +/** + * Build the request for creating index + * + * @param {String} indexName the index name + * @param {String} docType docType for index + * + * @return {Object} create index request + */ +function buildCreateIndexRequest(indexName, docType) { + const indexMapping = MAPPINGS[indexName]; + + if (!indexMapping) { + throw new Error(`Mapping is not found for index name '${indexName}'.`); + } + + const indexCreateRequest = { + index: indexName, + updateAllTypes: true, + body: { + mappings: {}, + }, + }; + indexCreateRequest.body.mappings[docType] = indexMapping; + + return indexCreateRequest; +} + module.exports = { indexMetadata, + buildCreateIndexRequest, }; From 4fa958d81ee49b42078cc2ba343bc62ffa620c05 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 11 Nov 2019 18:10:16 +0800 Subject: [PATCH 61/88] feat: admin endpoints to migrate data from DB to ES (metadata) At the moment it only supports "metadata" index. --- src/routes/admin/es-migrate-from-db.js | 46 ++++++++++++++++++++++++++ src/routes/index.js | 2 ++ 2 files changed, 48 insertions(+) create mode 100644 src/routes/admin/es-migrate-from-db.js diff --git a/src/routes/admin/es-migrate-from-db.js b/src/routes/admin/es-migrate-from-db.js new file mode 100644 index 00000000..eec4a4a2 --- /dev/null +++ b/src/routes/admin/es-migrate-from-db.js @@ -0,0 +1,46 @@ +/** + * Admin endpoint migrate data from DB to ES index. + * + * Waits until the operation is completed and returns result. + */ +import _ from 'lodash'; +import config from 'config'; +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import esUtils from '../../utils/es'; + +const ES_METADATA_INDEX = config.get('elasticsearchConfig.metadataIndexName'); + +const permissions = tcMiddleware.permissions; + +module.exports = [ + permissions('project.admin'), + (req, res, next) => { + try { + const logger = req.log; + logger.debug('Entered Admin#migrateFromDb'); + + const indexName = _.get(req, 'body.indexName'); + logger.debug('indexName', indexName); + + if (!indexName) { + const apiErr = new Error('"indexName" is required.'); + apiErr.status = 400; + throw apiErr; + } + + if (indexName !== ES_METADATA_INDEX) { + const apiErr = new Error(`Only "indexName" === "${ES_METADATA_INDEX}" is supported for now.`); + apiErr.status = 400; + throw apiErr; + } + + esUtils.indexMetadata() + .then(() => { + res.status(200).json({ message: 'Data has been successfully migrated.' }); + }) + .catch(next); + } catch (err) { + next(err); + } + }, +]; diff --git a/src/routes/index.js b/src/routes/index.js index 63c36d0a..6d0a4dc5 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -85,6 +85,8 @@ router.route('/v5/projects/admin/es/createIndex') .post(require('./admin/es-create-index')); router.route('/v5/projects/admin/es/deleteIndex') .delete(require('./admin/es-delete-index')); +router.route('/v5/projects/admin/es/migrateFromDb') + .patch(require('./admin/es-migrate-from-db')); router.route('/v5/projects/admin/es/project/index') .post(require('./admin/project-index-create')); router.route('/v5/projects/admin/es/project/remove') From 17ca11c7e9aff4b86ac25cc4b6b7f5b91710c622 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 11 Nov 2019 18:53:25 +0800 Subject: [PATCH 62/88] feat: admin endpoint to fix metadata for ES --- migrations/fixMetadataForES.js | 47 ++++++++++++------- src/routes/admin/es-fix-metadata-for-es.js | 54 ++++++++++++++++++++++ src/routes/index.js | 2 + 3 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 src/routes/admin/es-fix-metadata-for-es.js diff --git a/migrations/fixMetadataForES.js b/migrations/fixMetadataForES.js index bc28aba7..ac851470 100644 --- a/migrations/fixMetadataForES.js +++ b/migrations/fixMetadataForES.js @@ -61,9 +61,11 @@ function updateScope(scope) { /** * Fix all projectTemplates. * - * @returns {undefined} + * @param {Object} logger logger + * + * @returns {Promise} resolved when dene */ -async function fixProjectTemplates() { +async function fixProjectTemplates(logger) { const projectTemplates = await models.ProjectTemplate.findAll(); for (const projectTemplate of projectTemplates) { if (projectTemplate.scope) { @@ -71,7 +73,7 @@ async function fixProjectTemplates() { if (!_.isEqual(updatedScope, projectTemplate.scope)) { projectTemplate.scope = updatedScope; await projectTemplate.save(); - console.log(`updated record of ProjectTemplate with id ${projectTemplate.id}`); + logger.info(`updated record of ProjectTemplate with id ${projectTemplate.id}`); } } } @@ -81,6 +83,7 @@ async function fixProjectTemplates() { * Update the required property of an object. * * @param {Object} data any object + * * @returns {undefined} */ function updateRequiredProperty(data) { @@ -125,9 +128,11 @@ function updateTemplate(template) { /** * Fix all productTemplates. * - * @returns {undefined} + * @param {Object} logger logger + * + * @returns {Promise} resolved when dene */ -async function fixProductTemplates() { +async function fixProductTemplates(logger) { const productTemplates = await models.ProductTemplate.findAll(); for (const productTemplate of productTemplates) { @@ -136,7 +141,7 @@ async function fixProductTemplates() { if (!_.isEqual(updatedTemplate, productTemplate.template)) { productTemplate.template = updatedTemplate; await productTemplate.save(); - console.log(`updated record of ProductTemplate with id ${productTemplate.id}`); + logger.info(`updated record of ProductTemplate with id ${productTemplate.id}`); } } } @@ -145,18 +150,26 @@ async function fixProductTemplates() { /** * Fix all metadata models. * + * @param {Object} logger logger + * * @returns {undefined} */ -async function fixMetadataForES() { - await fixProjectTemplates(); - await fixProductTemplates(); +async function fixMetadataForES(logger) { + await fixProjectTemplates(logger); + await fixProductTemplates(logger); +} + +if (!module.parent) { + fixMetadataForES(console) + .then(() => { + console.log('done!'); + process.exit(); + }).catch((err) => { + console.error('Error syncing database', err); + process.exit(1); + }); } -fixMetadataForES() - .then(() => { - console.log('done!'); - process.exit(); - }).catch((err) => { - console.error('Error syncing database', err); - process.exit(1); - }); +module.exports = { + fixMetadataForES, +}; diff --git a/src/routes/admin/es-fix-metadata-for-es.js b/src/routes/admin/es-fix-metadata-for-es.js new file mode 100644 index 00000000..cfa583bd --- /dev/null +++ b/src/routes/admin/es-fix-metadata-for-es.js @@ -0,0 +1,54 @@ +/** + * Admin endpoint to fix metadata in DB to be indexed in ES. + * + * Waits until the operation is completed and returns result. + */ +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import { fixMetadataForES } from '../../../migrations/fixMetadataForES'; + +const permissions = tcMiddleware.permissions; + +/** + * Create a simple logger to log into an array. + * + * @returns {Object} logger + */ +const createArrayLogger = () => { + const loggerMethods = ['trace', 'debug', 'info', 'warn', 'error']; + const log = []; + const logger = {}; + + loggerMethods.forEach((method) => { + logger[method] = (message) => { + log.push({ + level: method, + message, + }); + }; + }); + + logger.getLog = () => log; + + return logger; +}; + +module.exports = [ + permissions('project.admin'), + (req, res, next) => { + try { + const logger = req.log; + logger.debug('Entered Admin#fixMetadataForEs'); + + const arrayLogger = createArrayLogger(); + + fixMetadataForES(arrayLogger) + .then(() => { + arrayLogger.info('Data has been successfully fixed in DB.'); + res.status(200).json(arrayLogger.getLog()); + }) + .catch(next); + } catch (err) { + next(err); + } + }, +]; diff --git a/src/routes/index.js b/src/routes/index.js index 6d0a4dc5..162b3ab1 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -87,6 +87,8 @@ router.route('/v5/projects/admin/es/deleteIndex') .delete(require('./admin/es-delete-index')); router.route('/v5/projects/admin/es/migrateFromDb') .patch(require('./admin/es-migrate-from-db')); +router.route('/v5/projects/admin/es/fixMetadataForEs') + .patch(require('./admin/es-fix-metadata-for-es')); router.route('/v5/projects/admin/es/project/index') .post(require('./admin/project-index-create')); router.route('/v5/projects/admin/es/project/remove') From ee5771e7c4f49936d08d8af119aca362422d40d7 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Thu, 14 Nov 2019 16:39:46 +0800 Subject: [PATCH 63/88] fix: fixMetadataForES script to be deployable --- migrations/fixMetadataForES.js | 176 ++------------------- src/routes/admin/es-fix-metadata-for-es.js | 2 +- src/utils/fixMetadataForES.js | 161 +++++++++++++++++++ 3 files changed, 172 insertions(+), 167 deletions(-) create mode 100644 src/utils/fixMetadataForES.js diff --git a/migrations/fixMetadataForES.js b/migrations/fixMetadataForES.js index ac851470..3f9dc725 100644 --- a/migrations/fixMetadataForES.js +++ b/migrations/fixMetadataForES.js @@ -1,7 +1,4 @@ -/* eslint-disable no-console */ -/* eslint-disable no-param-reassign */ -/* eslint-disable no-restricted-syntax */ -/* eslint-disable no-await-in-loop */ +/* eslint-disable no-console */ /** * Update all records in the ProjectTemplate table. * - inside “scope” field update “buildingBlocks..price” (for any ) to be a string if it’s not a string. @@ -10,166 +7,13 @@ * Update all records in the ProductTemplate table. * - inside "template" field update all "required" properties which is not of "boolean" type to boolean. */ -import _ from 'lodash'; -import models from '../src/models'; +import fixMetadataForES from '../src/utils/fixMetadataForES'; -/** - * Update the wizard property of an object. - * - * @param {Object} data any object - * @returns {undefined} - */ -function updateWizardProperty(data) { - if (typeof data.wizard === 'boolean') { - data.wizard = { enabled: data.wizard }; - } -} - -/** - * Update the scope property of a projectTemplate. - * - * @param {Object} scope the scope property - * @returns {Object} the updated scope - */ -function updateScope(scope) { - // update price properties - if (scope.buildingBlocks) { - for (const key of Object.keys(scope.buildingBlocks)) { - const price = scope.buildingBlocks[key].price; - if (price !== undefined) { - if (typeof price !== 'string') { - scope.buildingBlocks[key].price = price.toString(); - } - } - } - } - // update wizard properties - updateWizardProperty(scope); - if (scope.sections) { - for (const section of scope.sections) { - updateWizardProperty(section); - if (section.subSections) { - for (const subSection of section.subSections) { - updateWizardProperty(subSection); - } - } - } - } - return scope; -} - -/** - * Fix all projectTemplates. - * - * @param {Object} logger logger - * - * @returns {Promise} resolved when dene - */ -async function fixProjectTemplates(logger) { - const projectTemplates = await models.ProjectTemplate.findAll(); - for (const projectTemplate of projectTemplates) { - if (projectTemplate.scope) { - const updatedScope = updateScope(JSON.parse(JSON.stringify(projectTemplate.scope))); - if (!_.isEqual(updatedScope, projectTemplate.scope)) { - projectTemplate.scope = updatedScope; - await projectTemplate.save(); - logger.info(`updated record of ProjectTemplate with id ${projectTemplate.id}`); - } - } - } -} - -/** - * Update the required property of an object. - * - * @param {Object} data any object - * - * @returns {undefined} - */ -function updateRequiredProperty(data) { - if (typeof data.required !== 'undefined' && typeof data.required !== 'boolean') { - if (data.required === 'false') { - data.required = false; - } else if (data.required === 'true') { - data.required = true; - } else { - throw new Error(`"required" value ${data.required} cannot be converted to boolean.`); - } - } -} - -/** - * Update the template property of a productTemplate. - * - * @param {Object} template the template property - * @returns {Object} the updated template - */ -function updateTemplate(template) { - // update wizard properties - updateRequiredProperty(template); - if (template.sections) { - for (const section of template.sections) { - updateRequiredProperty(section); - if (section.subSections) { - for (const subSection of section.subSections) { - updateRequiredProperty(subSection); - if (subSection.questions) { - for (const question of subSection.questions) { - updateRequiredProperty(question); - } - } - } - } - } - } - return template; -} - -/** - * Fix all productTemplates. - * - * @param {Object} logger logger - * - * @returns {Promise} resolved when dene - */ -async function fixProductTemplates(logger) { - const productTemplates = await models.ProductTemplate.findAll(); - - for (const productTemplate of productTemplates) { - if (productTemplate.template) { - const updatedTemplate = updateTemplate(JSON.parse(JSON.stringify(productTemplate.template))); - if (!_.isEqual(updatedTemplate, productTemplate.template)) { - productTemplate.template = updatedTemplate; - await productTemplate.save(); - logger.info(`updated record of ProductTemplate with id ${productTemplate.id}`); - } - } - } -} - -/** - * Fix all metadata models. - * - * @param {Object} logger logger - * - * @returns {undefined} - */ -async function fixMetadataForES(logger) { - await fixProjectTemplates(logger); - await fixProductTemplates(logger); -} - -if (!module.parent) { - fixMetadataForES(console) - .then(() => { - console.log('done!'); - process.exit(); - }).catch((err) => { - console.error('Error syncing database', err); - process.exit(1); - }); -} - -module.exports = { - fixMetadataForES, -}; +fixMetadataForES(console) + .then(() => { + console.log('done!'); + process.exit(); + }).catch((err) => { + console.error('Error syncing database', err); + process.exit(1); + }); diff --git a/src/routes/admin/es-fix-metadata-for-es.js b/src/routes/admin/es-fix-metadata-for-es.js index cfa583bd..013bb8a2 100644 --- a/src/routes/admin/es-fix-metadata-for-es.js +++ b/src/routes/admin/es-fix-metadata-for-es.js @@ -4,7 +4,7 @@ * Waits until the operation is completed and returns result. */ import { middleware as tcMiddleware } from 'tc-core-library-js'; -import { fixMetadataForES } from '../../../migrations/fixMetadataForES'; +import fixMetadataForES from '../../utils/fixMetadataForES'; const permissions = tcMiddleware.permissions; diff --git a/src/utils/fixMetadataForES.js b/src/utils/fixMetadataForES.js new file mode 100644 index 00000000..8d665727 --- /dev/null +++ b/src/utils/fixMetadataForES.js @@ -0,0 +1,161 @@ +/* eslint-disable no-param-reassign, no-restricted-syntax, no-await-in-loop */ +/** + * Temporary script to fix metadata in DB to be indexed in ES + * + * Update all records in the ProjectTemplate table. + * - inside “scope” field update “buildingBlocks..price” (for any ) to be a string if it’s not a string. + * - inside “scope” field replace all the ‘“wizard”: true’ with ‘“wizard”: {“enabled”: true}’, + * and ‘“wizard”: false’ replace with ‘“wizard”: {“enabled”: false}’. + * Update all records in the ProductTemplate table. + * - inside "template" field update all "required" properties which is not of "boolean" type to boolean. + */ +import _ from 'lodash'; +import models from '../models'; + +/** + * Update the wizard property of an object. + * + * @param {Object} data any object + * @returns {undefined} + */ +function updateWizardProperty(data) { + if (typeof data.wizard === 'boolean') { + data.wizard = { enabled: data.wizard }; + } +} + +/** + * Update the scope property of a projectTemplate. + * + * @param {Object} scope the scope property + * @returns {Object} the updated scope + */ +function updateScope(scope) { + // update price properties + if (scope.buildingBlocks) { + for (const key of Object.keys(scope.buildingBlocks)) { + const price = scope.buildingBlocks[key].price; + if (price !== undefined) { + if (typeof price !== 'string') { + scope.buildingBlocks[key].price = price.toString(); + } + } + } + } + // update wizard properties + updateWizardProperty(scope); + if (scope.sections) { + for (const section of scope.sections) { + updateWizardProperty(section); + if (section.subSections) { + for (const subSection of section.subSections) { + updateWizardProperty(subSection); + } + } + } + } + return scope; +} + +/** + * Fix all projectTemplates. + * + * @param {Object} logger logger + * + * @returns {Promise} resolved when dene + */ +async function fixProjectTemplates(logger) { + const projectTemplates = await models.ProjectTemplate.findAll(); + for (const projectTemplate of projectTemplates) { + if (projectTemplate.scope) { + const updatedScope = updateScope(JSON.parse(JSON.stringify(projectTemplate.scope))); + if (!_.isEqual(updatedScope, projectTemplate.scope)) { + projectTemplate.scope = updatedScope; + await projectTemplate.save(); + logger.info(`updated record of ProjectTemplate with id ${projectTemplate.id}`); + } + } + } +} + +/** + * Update the required property of an object. + * + * @param {Object} data any object + * + * @returns {undefined} + */ +function updateRequiredProperty(data) { + if (typeof data.required !== 'undefined' && typeof data.required !== 'boolean') { + if (data.required === 'false') { + data.required = false; + } else if (data.required === 'true') { + data.required = true; + } else { + throw new Error(`"required" value ${data.required} cannot be converted to boolean.`); + } + } +} + +/** + * Update the template property of a productTemplate. + * + * @param {Object} template the template property + * @returns {Object} the updated template + */ +function updateTemplate(template) { + // update wizard properties + updateRequiredProperty(template); + if (template.sections) { + for (const section of template.sections) { + updateRequiredProperty(section); + if (section.subSections) { + for (const subSection of section.subSections) { + updateRequiredProperty(subSection); + if (subSection.questions) { + for (const question of subSection.questions) { + updateRequiredProperty(question); + } + } + } + } + } + } + return template; +} + +/** + * Fix all productTemplates. + * + * @param {Object} logger logger + * + * @returns {Promise} resolved when dene + */ +async function fixProductTemplates(logger) { + const productTemplates = await models.ProductTemplate.findAll(); + + for (const productTemplate of productTemplates) { + if (productTemplate.template) { + const updatedTemplate = updateTemplate(JSON.parse(JSON.stringify(productTemplate.template))); + if (!_.isEqual(updatedTemplate, productTemplate.template)) { + productTemplate.template = updatedTemplate; + await productTemplate.save(); + logger.info(`updated record of ProductTemplate with id ${productTemplate.id}`); + } + } + } +} + +/** + * Fix all metadata models. + * + * @param {Object} logger logger + * + * @returns {undefined} + */ +async function fixMetadataForES(logger) { + await fixProjectTemplates(logger); + await fixProductTemplates(logger); +} + +module.exports = fixMetadataForES; From fb9eb94787d4ff29bdcc879e8ac4267a52c7fbd5 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 19 Nov 2019 11:05:44 +0800 Subject: [PATCH 64/88] refactor: improve GET /orgConfigs endpoint unit test Apply the same unit tests when getting data from DB and ES, so we can be sure that this endpoint works same when data comes from both of these sources. The unit test by itself reuses most of the code now, so not need to copy/paste and adjust unit tests for DB and ES. With the current format it's much easier to write new unit tests. --- src/routes/orgConfig/list.spec.js | 371 ++++++++++++++---------------- src/utils/es.js | 1 + 2 files changed, 170 insertions(+), 202 deletions(-) diff --git a/src/routes/orgConfig/list.spec.js b/src/routes/orgConfig/list.spec.js index 498f1187..831a4898 100644 --- a/src/routes/orgConfig/list.spec.js +++ b/src/routes/orgConfig/list.spec.js @@ -3,18 +3,14 @@ */ import chai from 'chai'; import request from 'supertest'; -import config from 'config'; -import _ from 'lodash'; import models from '../../models'; import server from '../../app'; import testUtil from '../../tests/util'; +import esUtils from '../../utils/es'; const should = chai.should(); -const ES_ORGCONFIG_INDEX = config.get('elasticsearchConfig.metadataIndexName'); -const ES_ORGCONFIG_TYPE = config.get('elasticsearchConfig.metadataDocType'); - const validateOrgConfig = (resJson, orgConfig) => { resJson.id.should.be.eql(orgConfig.id); resJson.orgId.should.be.eql(orgConfig.orgId); @@ -27,215 +23,186 @@ const validateOrgConfig = (resJson, orgConfig) => { should.not.exist(resJson.deletedAt); }; +const orgConfigPath = '/v5/projects/metadata/orgConfig'; + +const configs = [ + { + id: 1, + orgId: 'ORG1', + configName: 'project_category_url', + configValue: '/projects/1', + createdBy: 1, + updatedBy: 1, + }, + { + id: 2, + orgId: 'ORG2', + configName: 'project_catalog_url', + configValue: '/projects/2', + createdBy: 1, + updatedBy: 1, + }, + { + id: 3, + orgId: 'ORG3', + configName: 'project_catalog_url', + configValue: '/projects/3', + createdBy: 1, + updatedBy: 1, + }, + { + id: 4, + orgId: 'ORG4', + configName: 'project_catalog_url', + configValue: '/projects/4', + createdBy: 1, + updatedBy: 1, + }, +]; + describe('LIST organization config', () => { - const orgConfigPath = '/v5/projects/metadata/orgConfig'; - const configs = [ - { - id: 1, - orgId: 'ORG1', - configName: 'project_category_url', - configValue: '/projects/1', - createdBy: 1, - updatedBy: 1, - }, - { - id: 2, - orgId: 'ORG2', - configName: 'project_catalog_url', - configValue: '/projects/2', - createdBy: 1, - updatedBy: 1, - }, - { - id: 3, - orgId: 'ORG3', - configName: 'project_catalog_url', - configValue: '/projects/3', - createdBy: 1, - updatedBy: 1, - }, - { - id: 4, - orgId: 'ORG4', - configName: 'project_catalog_url', - configValue: '/projects/4', - createdBy: 1, - updatedBy: 1, - }, - ]; - - beforeEach((done) => { - testUtil.clearDb() - .then(() => models.OrgConfig.bulkCreate(configs, { returning: true }), - ).then(async (createdConfigs) => { - // Index to ES only orgConfigs with id: 3 and 4 - const indexedConfigs = _(createdConfigs).filter(createdConfig => createdConfig.toJSON().id > 2) - .map((filteredConfig) => { - const orgConfigJson = _.omit(filteredConfig.toJSON(), 'deletedAt', 'deletedBy'); - return orgConfigJson; - }).value(); - - await server.services.es.index({ - index: ES_ORGCONFIG_INDEX, - type: ES_ORGCONFIG_TYPE, - body: { - orgConfigs: indexedConfigs, - }, - }); - done(); - }); - }); after((done) => { - testUtil.clearDb(done); + // clear data after tests in DB and ES + testUtil.clearDb() + .then(() => testUtil.clearES()) + .then(done); }); describe('GET /orgConfig', () => { - it('should return 200 for admin with filter', (done) => { - request(server) - .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect(200) - .end((err, res) => { - const config1 = configs[0]; - - const resJson = res.body; - resJson.should.have.length(1); - validateOrgConfig(resJson[0], config1); - - done(); - }); + describe('permissions', () => { + it('should return 403 if user is not authenticated with filter', (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) + .expect(403, done); + }); + + it('should return 200 for connect admin with filter', (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, + }) + .expect(200, done); + }); + + it('should return 200 for connect manager with filter', (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.manager}`, + }) + .expect(200, done); + }); + + it('should return 200 for member with filter', (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.member}`, + }) + .expect(200, done); + }); + + it('should return 200 for copilot with filter', (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.copilot}`, + }) + .expect(200, done); + }); + + it('should return 400 without filter query param', (done) => { + request(server) + .get(`${orgConfigPath}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(400, done); + }); + + it('should return 400 with filter query param but without orgId defined', (done) => { + request(server) + .get(`${orgConfigPath}?filter=configName=${configs[0].configName}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(400, done); + }); }); - it('should return 200 for admin with filter (ES)', (done) => { - request(server) - .get(`${orgConfigPath}?orgId=${configs[2].orgId}&configName=${configs[2].configName}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect(200) - .end((err, res) => { - const config3 = configs[2]; - - const resJson = res.body; - resJson.should.have.length(1); - validateOrgConfig(resJson[0], config3); - - done(); + // we are testing all the endpoints with the same data when it comes from DB and ES + ['ES', 'DB'].forEach((dataSource) => { + describe(`data from ${dataSource}`, () => { + before((done) => { + // clear data in DB and ES before tests + testUtil.clearDb() + .then(() => testUtil.clearES()) + // create data in DB first + .then(() => models.OrgConfig.bulkCreate(configs)) + .then(() => { + // if we want to test data in ES, then we index data from DB to ES + // and clear data in DB after that, so we only have data in ES + if (dataSource === 'ES') { + return esUtils.indexMetadata() + .then(() => testUtil.clearDb()); + } + return Promise.resolve(); + }) + .then(() => done()); }); - }); - it('should return 200 for admin and filter by multiple orgId (DB)', (done) => { - request(server) - .get(`${orgConfigPath}?orgId=${configs[0].orgId},${configs[1].orgId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect(200) - .end((err, res) => { - const config1 = configs[0]; - const config2 = configs[1]; - - const resJson = res.body; - resJson.should.have.length(2); - resJson.forEach((result) => { - if (result.id === 1) { - validateOrgConfig(result, config1); - } else { - validateOrgConfig(result, config2); - } - }); - - done(); + it(`should get one record for admin with filter (${dataSource})`, (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + const config1 = configs[0]; + + const resJson = res.body; + resJson.should.have.length(1); + validateOrgConfig(resJson[0], config1); + + return done(); + }); }); - }); - it('should return 200 for admin and filter by multiple orgId (ES)', (done) => { - request(server) - .get(`${orgConfigPath}?orgId=${configs[2].orgId},${configs[3].orgId}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect(200) - .end((err, res) => { - const config3 = configs[2]; - const config4 = configs[3]; - - const resJson = res.body; - resJson.should.have.length(2); - resJson.forEach((result) => { - if (result.id === 3) { - validateOrgConfig(result, config3); - } else { - validateOrgConfig(result, config4); - } - }); - - done(); + it(`should return 2 records for admin with filter by multiple orgId (${dataSource})`, (done) => { + request(server) + .get(`${orgConfigPath}?orgId=${configs[0].orgId},${configs[1].orgId}`) + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + const config1 = configs[0]; + const config2 = configs[1]; + + const resJson = res.body; + resJson.should.have.length(2); + resJson.forEach((result) => { + if (result.id === 1) { + validateOrgConfig(result, config1); + } else { + validateOrgConfig(result, config2); + } + }); + + return done(); + }); }); - }); - - it('should return 403 if user is not authenticated with filter', (done) => { - request(server) - .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) - .expect(403, done); - }); - - it('should return 200 for connect admin with filter', (done) => { - request(server) - .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.connectAdmin}`, - }) - .expect(200) - .end(done); - }); - - it('should return 200 for connect manager with filter', (done) => { - request(server) - .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.manager}`, - }) - .expect(200) - .end(done); - }); - - it('should return 200 for member with filter', (done) => { - request(server) - .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.member}`, - }) - .expect(200, done); - }); - - it('should return 200 for copilot with filter', (done) => { - request(server) - .get(`${orgConfigPath}?orgId=${configs[0].orgId}&configName=${configs[0].configName}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.copilot}`, - }) - .expect(200, done); - }); - - it('should return 400 without filter query param', (done) => { - request(server) - .get(`${orgConfigPath}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect(400, done); - }); - - it('should return 400 with filter query param but without orgId defined', (done) => { - request(server) - .get(`${orgConfigPath}?filter=configName=${configs[0].configName}`) - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .expect(400, done); + }); }); }); }); diff --git a/src/utils/es.js b/src/utils/es.js index c25e7395..c404001a 100644 --- a/src/utils/es.js +++ b/src/utils/es.js @@ -68,6 +68,7 @@ async function indexMetadata() { index: ES_METADATA_INDEX, type: ES_METADATA_TYPE, body, + refresh: 'wait_for', }); } From 8850389d771d0eb0bd504bbe67359b98fcff305f Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Tue, 19 Nov 2019 10:27:28 +0530 Subject: [PATCH 65/88] Corrected error message --- src/routes/projects/delete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/projects/delete.js b/src/routes/projects/delete.js index 65e0caac..bdbf7b03 100644 --- a/src/routes/projects/delete.js +++ b/src/routes/projects/delete.js @@ -20,7 +20,7 @@ module.exports = [ models.Project.findByPk(req.params.projectId) .then((entity) => { if (!entity) { - const apiErr = new Error(`Project template not found for template id ${projectId}`); + const apiErr = new Error(`Project not found for id ${projectId}`); apiErr.status = 404; return Promise.reject(apiErr); } From 8bf854fe7e41921f80cead2041d388c7ee023da6 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 19 Nov 2019 16:19:31 +0800 Subject: [PATCH 66/88] fix: Kafka events with correct type "action" --- .../helpers/indexMetadataByProcessor.js | 4 +- src/constants.js | 60 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/migrations/helpers/indexMetadataByProcessor.js b/migrations/helpers/indexMetadataByProcessor.js index c0689605..b042eed1 100644 --- a/migrations/helpers/indexMetadataByProcessor.js +++ b/migrations/helpers/indexMetadataByProcessor.js @@ -14,7 +14,7 @@ */ import _ from 'lodash'; import models from '../../src/models'; -import { RESOURCES } from '../../src/constants'; +import { RESOURCES, BUS_API_EVENT } from '../../src/constants'; import { createEvent } from '../../src/services/busApi'; const modelConfigs = { @@ -79,7 +79,7 @@ async function syncMetadataIndex() { await Promise.all( // eslint-disable-line no-await-in-loop records.map(record => createEvent( - 'project.notification.create', + BUS_API_EVENT.PROJECT_METADATA_CREATE, _.assign({ resource: modelConfig.resource }, record), console, ), diff --git a/src/constants.js b/src/constants.js index 0c77c7e5..a31c965b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -124,52 +124,52 @@ export const EVENT = { }; export const BUS_API_EVENT = { - PROJECT_CREATED: 'project.notification.create', - PROJECT_UPDATED: 'project.notification.update', - PROJECT_DELETED: 'project.notification.delete', + PROJECT_CREATED: 'project.action.create', + PROJECT_UPDATED: 'project.action.update', + PROJECT_DELETED: 'project.action.delete', - PROJECT_MEMBER_ADDED: 'project.notification.create', - PROJECT_MEMBER_REMOVED: 'project.notification.delete', - PROJECT_MEMBER_UPDATED: 'project.notification.update', + PROJECT_MEMBER_ADDED: 'project.action.create', + PROJECT_MEMBER_REMOVED: 'project.action.delete', + PROJECT_MEMBER_UPDATED: 'project.action.update', - PROJECT_ATTACHMENT_ADDED: 'project.notification.create', - PROJECT_ATTACHMENT_REMOVED: 'project.notification.delete', - PROJECT_ATTACHMENT_UPDATED: 'project.notification.update', + PROJECT_ATTACHMENT_ADDED: 'project.action.create', + PROJECT_ATTACHMENT_REMOVED: 'project.action.delete', + PROJECT_ATTACHMENT_UPDATED: 'project.action.update', // When phase is added/updated/deleted from the project, // When product is added/deleted from a phase // When product is updated on any field other than specification - PROJECT_PHASE_CREATED: 'project.notification.create', - PROJECT_PHASE_UPDATED: 'project.notification.update', - PROJECT_PHASE_DELETED: 'project.notification.delete', + PROJECT_PHASE_CREATED: 'project.action.create', + PROJECT_PHASE_UPDATED: 'project.action.update', + PROJECT_PHASE_DELETED: 'project.action.delete', // phase product - PROJECT_PHASE_PRODUCT_ADDED: 'project.notification.create', - PROJECT_PHASE_PRODUCT_UPDATED: 'project.notification.update', - PROJECT_PHASE_PRODUCT_REMOVED: 'project.notification.delete', + PROJECT_PHASE_PRODUCT_ADDED: 'project.action.create', + PROJECT_PHASE_PRODUCT_UPDATED: 'project.action.update', + PROJECT_PHASE_PRODUCT_REMOVED: 'project.action.delete', // timeline - TIMELINE_CREATED: 'project.notification.create', - TIMELINE_UPDATED: 'project.notification.update', - TIMELINE_DELETED: 'project.notification.delete', + TIMELINE_CREATED: 'project.action.create', + TIMELINE_UPDATED: 'project.action.update', + TIMELINE_DELETED: 'project.action.delete', - MILESTONE_ADDED: 'project.notification.create', - MILESTONE_REMOVED: 'project.notification.delete', - MILESTONE_UPDATED: 'project.notification.update', + MILESTONE_ADDED: 'project.action.create', + MILESTONE_REMOVED: 'project.action.delete', + MILESTONE_UPDATED: 'project.action.update', - MILESTONE_TEMPLATE_ADDED: 'project.notification.create', - MILESTONE_TEMPLATE_REMOVED: 'project.notification.delete', - MILESTONE_TEMPLATE_UPDATED: 'project.notification.update', + MILESTONE_TEMPLATE_ADDED: 'project.action.create', + MILESTONE_TEMPLATE_REMOVED: 'project.action.delete', + MILESTONE_TEMPLATE_UPDATED: 'project.action.update', // Project Member Invites - PROJECT_MEMBER_INVITE_CREATED: 'project.notification.create', - PROJECT_MEMBER_INVITE_UPDATED: 'project.notification.update', - PROJECT_MEMBER_INVITE_REMOVED: 'project.notification.delete', + PROJECT_MEMBER_INVITE_CREATED: 'project.action.create', + PROJECT_MEMBER_INVITE_UPDATED: 'project.action.update', + PROJECT_MEMBER_INVITE_REMOVED: 'project.action.delete', // metadata - PROJECT_METADATA_CREATE: 'project.notification.create', - PROJECT_METADATA_UPDATE: 'project.notification.update', - PROJECT_METADATA_DELETE: 'project.notification.delete', + PROJECT_METADATA_CREATE: 'project.action.create', + PROJECT_METADATA_UPDATE: 'project.action.update', + PROJECT_METADATA_DELETE: 'project.action.delete', }; export const CONNECT_NOTIFICATION_EVENT = { From 70ef4b569dd7d48e888c7dd06de23382883bec65 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 20 Nov 2019 18:19:04 +0800 Subject: [PATCH 67/88] fix: analytics events --- src/events/analytics.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/events/analytics.js b/src/events/analytics.js index 8574f5d7..0b10c975 100644 --- a/src/events/analytics.js +++ b/src/events/analytics.js @@ -60,8 +60,8 @@ module.exports = (analyticsKey, app) => { }); }); - app.on(EVENT.ROUTING_KEY.PROJECT_DELETED, ({ req, id }) => { - track(req, PROJECT_CREATED, { id }); + app.on(EVENT.ROUTING_KEY.PROJECT_DELETED, ({ req, project }) => { + track(req, PROJECT_CREATED, { id: project.id }); }); app.on(EVENT.ROUTING_KEY.PROJECT_UPDATED, ({ req, original, updated }) => { @@ -84,11 +84,15 @@ module.exports = (analyticsKey, app) => { } }); - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, member }) => { + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_ADDED, ({ req, resource }) => { + const member = _.omit(resource, 'resource'); + track(req, PROJECT_MEMBER_ADDED, { role: member.role }); }); - app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, member }) => { + app.on(EVENT.ROUTING_KEY.PROJECT_MEMBER_REMOVED, ({ req, resource }) => { + const member = _.omit(resource, 'resource'); + track(req, PROJECT_MEMBER_REMOVED, { role: member.role }); }); }; From ae2656e8269a347e8292b1ddc7826d78d71c1609 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Thu, 21 Nov 2019 11:15:58 +0530 Subject: [PATCH 68/88] Restoring non v5 call to file service because file service is yet to receive the v5 standards. --- src/routes/attachments/create.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index 6b36ca68..eec28327 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -115,7 +115,9 @@ module.exports = [ // retrieve download url for the response req.log.debug('retrieving download url'); return httpClient.post(`${fileServiceUrl}downloadurl`, { - filePath, + param: { + filePath, + } }); } return Promise.resolve(); From b8d93824124c1d5da4f25bf032b53fdd248038db Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Thu, 21 Nov 2019 11:18:39 +0530 Subject: [PATCH 69/88] Lint fix --- src/routes/attachments/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/attachments/create.js b/src/routes/attachments/create.js index eec28327..be69ab48 100644 --- a/src/routes/attachments/create.js +++ b/src/routes/attachments/create.js @@ -117,7 +117,7 @@ module.exports = [ return httpClient.post(`${fileServiceUrl}downloadurl`, { param: { filePath, - } + }, }); } return Promise.resolve(); From 27c8a937a23082479bfa696e372f93861690f5c4 Mon Sep 17 00:00:00 2001 From: meshde Date: Thu, 28 Nov 2019 17:23:45 +0530 Subject: [PATCH 70/88] Add local docker configuration setup with multiple tc services --- README.md | 74 ++++++++++ local/full/docker-compose.base.yml | 26 ++++ local/full/docker-compose.yml | 129 ++++++++++++++++++ local/full/generic-tc-service/Dockerfile | 15 ++ .../generic-tc-service/docker-entrypoint.sh | 19 +++ local/full/kafka-client/Dockerfile | 5 + local/full/kafka-client/create-topics.sh | 5 + local/full/kafka-client/topics.txt | 55 ++++++++ 8 files changed, 328 insertions(+) create mode 100644 local/full/docker-compose.base.yml create mode 100644 local/full/docker-compose.yml create mode 100644 local/full/generic-tc-service/Dockerfile create mode 100755 local/full/generic-tc-service/docker-entrypoint.sh create mode 100644 local/full/kafka-client/Dockerfile create mode 100755 local/full/kafka-client/create-topics.sh create mode 100644 local/full/kafka-client/topics.txt diff --git a/README.md b/README.md index e2790ac4..2a764cab 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Microservice to manage CRUD operations for all things Projects. ``` Alternatively, you may update `config/local.js` and replace `dockerhost` with your docker IP address.
You may try using command `docker-machine ip` to get your docker IP, but it works not for all systems. + Also, be sure to update `busApiUrl` if you are running `tc-bus-api` locally. (See below) Explanation of configs: - `config/mock.local.js` - Use local `mock-services` from docker to mock Identity and Member services instead of using deployed at Topcoder dev environment. @@ -92,6 +93,47 @@ This command will create sample metadata entries in the DB (duplicate what is cu To retrieve data from DEV env we need to provide a valid user token. You may login to http://connect.topcoder-dev.com and find the Bearer token in the request headers using browser dev tools. +### Local Deployment with other Topcoder Services. + +* There exists an alternate `docker-compose.yml` file that can be used to spawn containers for the following services: + +| Service | Name | Port | +|----------|:-----:|:----:| +| PostGreSQL DB | db | 5432 | +| ElasticSearch | esearch | 9200,9300 | +| RabbitMQ | queue | 5672, 15672 | +| Zookeeper | zookeeper | 2181 | +| Kafka | kafka | 9092 | +| [tc-bus-api](https://github.com/topcoder-platform/tc-bus-api) | tc-bus-api | 8002 | +| [project-processor-es](https://github.com/topcoder-platform/project-processor-es) | project-processor-es | 5000 | +| [tc-notifications-api](https://github.com/topcoder-platform/tc-notifications) | tc-notifications-api | 4000 | +| [tc-notifications-processor](https://github.com/topcoder-platform/tc-notifications) | tc-notifications-processor | 4001 | + +* To have kafka create a list of desired topics on startup, there exists a file with the path `local/full/kafka-client/topics.txt`. Each line from the file will be added as a topic. +* To run these services simply run the following commands: + + ```bash + export AUTH0_CLIENT_ID= + export AUTH0_CLIENT_SECRET= + export AUTH0_URL= + export AUTH0_AUDIENCE= + export AUTH0_PROXY_SERVER_URL= + + cd local/full + docker-compose up -d + ``` + +* The environment variables specified in the commands above will be passed onto the containers that have been configured to read them. +* The above command will start all containers in the background. +* To view the logs of any of the services use the following command, replacing "SERVICE_NAME" with the corresponding value under the "Name" column in the above table: + + ```bash + cd local/full + docker-compose logs -f SERVICE_NAME + ``` + +* The containers have been configured such that all Topcoder services will wait until all the topics listed in `local/full/kafka-client/topics.txt` have been created. To monitor the progress of topic creation, you can view the logs of the `kafka-client` service, which will exit when all topics have been created. + ### Run Connect App with Project Service locally To be able to run [Connect App](https://github.com/appirio-tech/connect-app) with the local setup of Project Service we have to do two things: @@ -99,7 +141,9 @@ To be able to run [Connect App](https://github.com/appirio-tech/connect-app) wit ```js PROJECTS_API_URL: 'http://localhost:8001' + TC_NOTIFICATION_URL: 'http://localhost:4000/v5/notifications' # if tc-notfication-api has been locally deployed ``` + 2. Bypass token validation in Project Service. In `tc-project-service/node_modules/tc-core-library-js/lib/auth/verifier.js` add this to line 23: @@ -142,3 +186,33 @@ You can paste **swagger.yaml** to [swagger editor](http://editor.swagger.io/) o #### Deploying without docker If you don't want to use docker to deploy to localhost. You can simply run `npm run start:dev` from root of project. This should start the server on default port `8001`. + +### Kafka Commands + +If you've used `docker-compose` with the file `local/full/docker-compose.yml` to spawn kafka & zookeeper, you can use the following commands to manipulate kafka topics and messages: +(Replace TOPIC_NAME with the name of the desired topic) + +**Create Topic** + +```bash +docker exec tc-projects-kafka /usr/bin/kafka-topics --create --zookeeper zookeeper:2181 --partitions 1 --replication-factor 1 --topic TOPIC_NAME +``` + +**List Topics** + +```bash +docker exec -it tc-projects-kafka /usr/bin/kafka-topics --list --zookeeper zookeeper:2181 +``` + +**Watch Topic** + +```bash +docker exec -it tc-projects-kafka /usr/bin/kafka-console-consumer --bootstrap-server localhost:9092 --zookeeper zookeeper:2181 --topic TOPIC_NAME +``` + +**Post Message to Topic** + +```bash +docker exec -it tc-projects-kafka /usr/bin/kafka-console-producer --topic TOPIC_NAME --broker-list localhost:9092 +``` +The message can be passed using `stdin` diff --git a/local/full/docker-compose.base.yml b/local/full/docker-compose.base.yml new file mode 100644 index 00000000..6020be65 --- /dev/null +++ b/local/full/docker-compose.base.yml @@ -0,0 +1,26 @@ +version: "2" +services: + tc-notifications-base: + build: + context: ./generic-tc-service + args: + NODE_VERSION: 8.2.1 + GIT_URL: https://github.com/topcoder-platform/tc-notifications + GIT_BRANCH: v5-upgrade + BYPASS_TOKEN_VALIDATION: 1 + environment: + - VALID_ISSUERS="[\"https://topcoder-newauth.auth0.com/\",\"https://api.topcoder-dev.com\"]" + - TC_API_V5_BASE_URL=http://host.docker.internal:8001/v5 + - TC_API_V4_BASE_URL=https://api.topcoder-dev.com/v4 + - TC_API_V3_BASE_URL=https://api.topcoder-dev.com/v3 + - KAFKA_URL=kafka:9092 + - AUTH_SECRET=secret + - DATABASE_URL=postgresql://coder:mysecretpassword@notifications_db:5432/tc_notifications + - JWKS_URI=test + - LOG_LEVEL=debug + - ENV=development + - AUTH0_CLIENT_ID + - AUTH0_CLIENT_SECRET + - AUTH0_URL + - AUTH0_AUDIENCE + - AUTH0_PROXY_SERVER_URL diff --git a/local/full/docker-compose.yml b/local/full/docker-compose.yml new file mode 100644 index 00000000..b6f0f207 --- /dev/null +++ b/local/full/docker-compose.yml @@ -0,0 +1,129 @@ +version: "2" +services: + jsonserver: + extends: + file: ../docker-compose.yml + service: jsonserver + db: + extends: + file: ../docker-compose.yml + service: db + notifications_db: + image: "postgres:9.5" + expose: + - "5432" + environment: + - POSTGRES_PASSWORD=mysecretpassword + - POSTGRES_USER=coder + - POSTGRES_DB=tc_notifications + db_test: + extends: + file: ../docker-compose.yml + service: db_test + esearch: + extends: + file: ../docker-compose.yml + service: esearch + queue: + extends: + file: ../docker-compose.yml + service: queue + zookeeper: + image: confluent/zookeeper + ports: + - "2181:2181" + environment: + zk_id: "1" + kafka: + image: confluent/kafka + container_name: tc-projects-kafka + depends_on: + - zookeeper + ports: + - "9092:9092" + kafka-client: + build: ./kafka-client + depends_on: + - kafka + - zookeeper + tc-bus-api: + build: + context: ./generic-tc-service + args: + NODE_VERSION: 8.2.1 + GIT_URL: https://github.com/topcoder-platform/tc-bus-api + GIT_BRANCH: dev + BYPASS_TOKEN_VALIDATION: 1 + command: start kafka-client + expose: + - "3000" + ports: + - "8002:3000" + depends_on: + - kafka-client + environment: + - PORT=3000 + - KAFKA_URL=kafka:9092 + - JWT_TOKEN_SECRET=secret + - VALID_ISSUERS="[\"https://topcoder-newauth.auth0.com/\",\"https://api.topcoder-dev.com\"]" + 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 + command: start kafka-client + expose: + - "5000" + ports: + - "5000:5000" + depends_on: + - kafka-client + environment: + - PORT=5000 + - KAFKA_URL=kafka:9092 + - ES_HOST=esearch:9200 + tc-notifications-reset-db: + extends: + file: ./docker-compose.base.yml + service: tc-notifications-base + command: reset:db + restart: on-failure + expose: + - "4002" + ports: + - "4002:4002" + depends_on: + - notifications_db + - esearch + environment: + - PORT=4002 + tc-notifications-processor: + extends: + file: ./docker-compose.base.yml + service: tc-notifications-base + command: start kafka-client + depends_on: + - tc-notifications-reset-db + - kafka-client + expose: + - "4001" + ports: + - "4001:4001" + environment: + - PORT=4001 + tc-notifications-api: + extends: + file: ./docker-compose.base.yml + service: tc-notifications-base + command: startAPI kafka-client + depends_on: + - tc-notifications-reset-db + - kafka-client + expose: + - "4000" + ports: + - "4000:4000" + environment: + - PORT=4000 diff --git a/local/full/generic-tc-service/Dockerfile b/local/full/generic-tc-service/Dockerfile new file mode 100644 index 00000000..3c93b79b --- /dev/null +++ b/local/full/generic-tc-service/Dockerfile @@ -0,0 +1,15 @@ +ARG NODE_VERSION=8.2.1 + +FROM node:$NODE_VERSION +ARG GIT_URL +ARG GIT_BRANCH +ARG BYPASS_TOKEN_VALIDATION + +RUN git clone $GIT_URL /opt/app +WORKDIR /opt/app +RUN git checkout -b node-branch origin/$GIT_BRANCH + +RUN npm install +RUN if [ $BYPASS_TOKEN_VALIDATION -eq 1 ]; then sed -i '/decodedToken = jwt.decode/a \ callback(undefined, decodedToken.payload); return;' node_modules/tc-core-library-js/lib/auth/verifier.js; fi +COPY docker-entrypoint.sh /opt/ +ENTRYPOINT ["/opt/docker-entrypoint.sh"] diff --git a/local/full/generic-tc-service/docker-entrypoint.sh b/local/full/generic-tc-service/docker-entrypoint.sh new file mode 100755 index 00000000..312b4f0a --- /dev/null +++ b/local/full/generic-tc-service/docker-entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +HOST_DOMAIN="host.docker.internal" +ping -q -c1 $HOST_DOMAIN > /dev/null 2>&1 +if [ $? -ne 0 ]; then + HOST_IP=$(ip route | awk 'NR==1 {print $3}') + echo -e "$HOST_IP\t$HOST_DOMAIN" >> /etc/hosts +fi + +if [ $# -eq 2 ]; then + echo "Waiting for $2 to exit...." + while ping -c1 $2 &>/dev/null + do + sleep 1 + done + echo "$2 exited!" +fi + +cd /opt/app/ && npm run $1 diff --git a/local/full/kafka-client/Dockerfile b/local/full/kafka-client/Dockerfile new file mode 100644 index 00000000..3d332b83 --- /dev/null +++ b/local/full/kafka-client/Dockerfile @@ -0,0 +1,5 @@ +From confluent/kafka +WORKDIR /app/ +COPY topics.txt . +COPY create-topics.sh . +ENTRYPOINT ["/app/create-topics.sh"] diff --git a/local/full/kafka-client/create-topics.sh b/local/full/kafka-client/create-topics.sh new file mode 100755 index 00000000..c5b33e62 --- /dev/null +++ b/local/full/kafka-client/create-topics.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +while read topic; do + /usr/bin/kafka-topics --create --zookeeper zookeeper:2181 --partitions 1 --replication-factor 1 --topic $topic +done < topics.txt diff --git a/local/full/kafka-client/topics.txt b/local/full/kafka-client/topics.txt new file mode 100644 index 00000000..85ac3f0f --- /dev/null +++ b/local/full/kafka-client/topics.txt @@ -0,0 +1,55 @@ +connect.notification.email.project.member.invite.created +connect.notification.project.active +connect.notification.project.approved +connect.notification.project.canceled +connect.notification.project.completed +connect.notification.project.created +connect.notification.project.fileUploaded +connect.notification.project.files.updated +connect.notification.project.linkCreated +connect.notification.project.member.assignedAsOwner +connect.notification.project.member.copilotJoined +connect.notification.project.member.invite.approved +connect.notification.project.member.invite.created +connect.notification.project.member.invite.rejected +connect.notification.project.member.invite.requested +connect.notification.project.member.invite.updated +connect.notification.project.member.joined +connect.notification.project.member.left +connect.notification.project.member.managerJoined +connect.notification.project.member.removed +connect.notification.project.paused +connect.notification.project.phase.transition.active +connect.notification.project.phase.transition.completed +connect.notification.project.phase.update.payment +connect.notification.project.phase.update.progress +connect.notification.project.phase.update.scope +connect.notification.project.plan.ready +connect.notification.project.plan.updated +connect.notification.project.post.created +connect.notification.project.post.edited +connect.notification.project.product.update.spec +connect.notification.project.submittedForReview +connect.notification.project.team.updated +connect.notification.project.timeline.adjusted +connect.notification.project.timeline.milestone.added +connect.notification.project.timeline.milestone.removed +connect.notification.project.timeline.milestone.transition.active +connect.notification.project.timeline.milestone.transition.completed +connect.notification.project.timeline.milestone.transition.paused +connect.notification.project.timeline.milestone.updated +connect.notification.project.timeline.milestone.waiting.customer +connect.notification.project.topic.created +connect.notification.project.topic.updated +connect.notification.project.updated +connect.notification.project.updated.progress +connect.notification.project.updated.spec +connect.notification.project.work.transition.active +connect.notification.project.work.transition.completed +connect.notification.project.work.update.payment +connect.notification.project.work.update.progress +connect.notification.project.work.update.scope +connect.notification.project.workitem.update.spec +project.notification.create +project.notification.delete +project.notification.update From fbf18a821ad1e68023eee9867880a718830fc84c Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Fri, 13 Dec 2019 20:08:34 +0800 Subject: [PATCH 71/88] fix: fixes after merging with dev --- src/routes/projectMemberInvites/get.js | 2 +- src/routes/projectMemberInvites/list.js | 4 ++-- src/routes/projectMembers/get.js | 3 +-- src/routes/projectMembers/list.js | 6 +++--- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/routes/projectMemberInvites/get.js b/src/routes/projectMemberInvites/get.js index 7fc4754b..2cc9fb1d 100644 --- a/src/routes/projectMemberInvites/get.js +++ b/src/routes/projectMemberInvites/get.js @@ -26,7 +26,7 @@ module.exports = [ const projectId = _.parseInt(req.params.projectId); const currentUserId = req.authUser.userId; const email = req.authUser.email; - const fields = req.query.field ? req.query.fields.split(',') : null; + const fields = req.query.fields ? req.query.fields.split(',') : null; util.fetchByIdFromES('invites', { query: { diff --git a/src/routes/projectMemberInvites/list.js b/src/routes/projectMemberInvites/list.js index f380dc19..b67eeae3 100644 --- a/src/routes/projectMemberInvites/list.js +++ b/src/routes/projectMemberInvites/list.js @@ -24,7 +24,7 @@ module.exports = [ permissions('projectMemberInvite.list'), (req, res, next) => { const projectId = _.parseInt(req.params.projectId); - const fields = req.query.field ? req.query.fields.split(',') : null; + const fields = req.query.fields ? req.query.fields.split(',') : null; util.fetchByIdFromES('invites', { query: { @@ -52,7 +52,7 @@ module.exports = [ return models.ProjectMemberInvite.getPendingAndReguestedInvitesForProject(projectId); } req.log.debug('project member found in ES'); - return data[0].inner_hits.invites.hits.hits; // eslint-disable-line no-underscore-dangle + 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) => { diff --git a/src/routes/projectMembers/get.js b/src/routes/projectMembers/get.js index ee99c5bc..d6768f86 100644 --- a/src/routes/projectMembers/get.js +++ b/src/routes/projectMembers/get.js @@ -30,7 +30,7 @@ module.exports = [ (req, res, next) => { const projectId = _.parseInt(req.params.projectId); const memberRecordId = _.parseInt(req.params.id); - const fields = req.query.field ? req.query.fields.split(',') : null; + const fields = req.query.fields ? req.query.fields.split(',') : null; util.fetchByIdFromES('members', { query: { @@ -68,7 +68,6 @@ module.exports = [ // check there is an existing member const err = new Error(`member not found for project id ${projectId}, id ${memberRecordId}`); err.status = 404; - console.log('ERRRRRRRR'); throw err; } return member; diff --git a/src/routes/projectMembers/list.js b/src/routes/projectMembers/list.js index 14f86f28..25314860 100644 --- a/src/routes/projectMembers/list.js +++ b/src/routes/projectMembers/list.js @@ -31,7 +31,7 @@ module.exports = [ permissions('project.viewMember'), (req, res, next) => { const projectId = _.parseInt(req.params.projectId); - const fields = req.query.field ? req.query.fields.split(',') : null; + const fields = req.query.fields ? req.query.fields.split(',') : null; const must = [ { term: { 'members.projectId': projectId } }, ]; @@ -63,7 +63,7 @@ module.exports = [ }) .then((data) => { if (data.length === 0) { - req.log.debug('No project member found in ES'); + req.log.debug('No project members found in ES'); // Get all project members const where = { projectId, @@ -81,7 +81,7 @@ module.exports = [ raw: true, }); } - req.log.debug('project member found in ES'); + 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 }) .then(members => ( From bacb4a0c11dac0782fa710ea199de7d2e6fa8382 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 16 Dec 2019 13:15:10 +0800 Subject: [PATCH 72/88] feat: array logger also use default logger to output to console --- src/routes/admin/es-fix-metadata-for-es.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/routes/admin/es-fix-metadata-for-es.js b/src/routes/admin/es-fix-metadata-for-es.js index 013bb8a2..1859ef0c 100644 --- a/src/routes/admin/es-fix-metadata-for-es.js +++ b/src/routes/admin/es-fix-metadata-for-es.js @@ -11,15 +11,20 @@ const permissions = tcMiddleware.permissions; /** * Create a simple logger to log into an array. * + * @param {Object} defaultLogger default logger which should be used apart from logging to array + * * @returns {Object} logger */ -const createArrayLogger = () => { +const createArrayLogger = (defaultLogger) => { const loggerMethods = ['trace', 'debug', 'info', 'warn', 'error']; const log = []; const logger = {}; loggerMethods.forEach((method) => { logger[method] = (message) => { + // log directly with the default logger first + defaultLogger[method](message); + // save the same message to the array log.push({ level: method, message, @@ -39,7 +44,9 @@ module.exports = [ const logger = req.log; logger.debug('Entered Admin#fixMetadataForEs'); - const arrayLogger = createArrayLogger(); + // this logger would use the default `logger` to log into console + // while saving the same log messages to an array, so we can return it in response + const arrayLogger = createArrayLogger(logger); fixMetadataForES(arrayLogger) .then(() => { From 41544422c0641630d738613a4abe5e4500e38d47 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 16 Dec 2019 17:35:35 +0800 Subject: [PATCH 73/88] fix: delete phase in ES --- src/routes/phases/delete.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/phases/delete.js b/src/routes/phases/delete.js index a7e6d2a0..5d1657bf 100644 --- a/src/routes/phases/delete.js +++ b/src/routes/phases/delete.js @@ -50,7 +50,8 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_PHASE_REMOVED, RESOURCES.PHASE, - _.pick(deleted.toJSON(), 'id')); + deleted.toJSON(), + ); res.status(204).json({}); }).catch(err => next(err)); From 1fd4d2ad6b57ce3ca53d14bf87a5863eb8af6fb3 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Mon, 16 Dec 2019 21:17:44 +0800 Subject: [PATCH 74/88] Revert "fix: temporary fix to index TaaS project with skills" This reverts commit 1c24d2ab1213591f220472272cd5a7eb77b2f32c. --- src/events/projects/index.js | 12 ------------ src/routes/admin/project-index-create.js | 4 ---- 2 files changed, 16 deletions(-) diff --git a/src/events/projects/index.js b/src/events/projects/index.js index bbab541f..ab53c704 100644 --- a/src/events/projects/index.js +++ b/src/events/projects/index.js @@ -41,10 +41,6 @@ const indexProject = Promise.coroutine(function* (logger, msg) { // eslint-disab // removes non required fields from phase objects data.phases = data.phases.map(phase => _.omit(phase, ['deletedAt', 'deletedBy'])); } - // TEMPORARY FIX: should fix ES mapping instead and reindex all the projects instead - if (typeof _.get(data, 'details.taasDefinition.team.skills') !== 'string') { - _.set(data, 'details.taasDefinition.team.skills', ''); - } // add the record to the index const result = yield eClient.index({ index: ES_PROJECT_INDEX, @@ -95,10 +91,6 @@ const projectUpdatedHandler = Promise.coroutine(function* (logger, msg, channel) // first get the existing document and than merge the updated changes and save the new document const doc = yield eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: data.original.id }); const merged = _.merge(doc._source, data.updated); // eslint-disable-line no-underscore-dangle - // TEMPORARY FIX: should fix ES mapping instead and reindex all the projects instead - if (typeof _.get(merged, 'details.taasDefinition.team.skills') !== 'string') { - _.set(merged, 'details.taasDefinition.team.skills', ''); - } // update the merged document yield eClient.update({ index: ES_PROJECT_INDEX, @@ -181,10 +173,6 @@ async function projectUpdatedKafkaHandler(app, topic, payload) { try { const doc = await eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: previousValue.id }); const merged = _.merge(doc._source, project.get({ plain: true })); // eslint-disable-line no-underscore-dangle - // TEMPORARY FIX: should fix ES mapping instead and reindex all the projects instead - if (typeof _.get(merged, 'details.taasDefinition.team.skills') !== 'string') { - _.set(merged, 'details.taasDefinition.team.skills', ''); - } // update the merged document await eClient.update({ index: ES_PROJECT_INDEX, diff --git a/src/routes/admin/project-index-create.js b/src/routes/admin/project-index-create.js index e710c692..a08bf87d 100644 --- a/src/routes/admin/project-index-create.js +++ b/src/routes/admin/project-index-create.js @@ -97,10 +97,6 @@ module.exports = [ projectResponses.map((p) => { if (p) { body.push({ index: { _index: indexName, _type: docType, _id: p.id } }); - // TEMPORARY FIX: should fix ES mapping instead and reindex all the projects instead - if (typeof _.get(p, 'details.taasDefinition.team.skills') !== 'string') { - _.set(p, 'details.taasDefinition.team.skills', ''); - } body.push(p); } // dummy return From 5f3e1d00e3d447cf2ce09534fb9a3a9ff4293d0f Mon Sep 17 00:00:00 2001 From: dengzikun Date: Mon, 16 Dec 2019 22:56:26 +0800 Subject: [PATCH 75/88] fix project data in DB --- src/routes/admin/es-fix-projects-for-es.js | 61 ++++++++++++++++++++++ src/routes/index.js | 2 + src/utils/fixProjectsForES.js | 45 ++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 src/routes/admin/es-fix-projects-for-es.js create mode 100644 src/utils/fixProjectsForES.js diff --git a/src/routes/admin/es-fix-projects-for-es.js b/src/routes/admin/es-fix-projects-for-es.js new file mode 100644 index 00000000..44987fc7 --- /dev/null +++ b/src/routes/admin/es-fix-projects-for-es.js @@ -0,0 +1,61 @@ +/** + * Admin endpoint to fix project in DB to be indexed in ES. + * + * Waits until the operation is completed and returns result. + */ +import { middleware as tcMiddleware } from 'tc-core-library-js'; +import fixProjectsForES from '../../utils/fixProjectsForES'; + +const permissions = tcMiddleware.permissions; + +/** + * Create a simple logger to log into an array. + * + * @param {Object} defaultLogger default logger which should be used apart from logging to array + * + * @returns {Object} logger + */ +const createArrayLogger = (defaultLogger) => { + const loggerMethods = ['trace', 'debug', 'info', 'warn', 'error']; + const log = []; + const logger = {}; + + loggerMethods.forEach((method) => { + logger[method] = (message) => { + // log directly with the default logger first + defaultLogger[method](message); + // save the same message to the array + log.push({ + level: method, + message, + }); + }; + }); + + logger.getLog = () => log; + + return logger; +}; + +module.exports = [ + permissions('project.admin'), + (req, res, next) => { + try { + const logger = req.log; + logger.debug('Entered Admin#fixProjectsForEs'); + + // this logger would use the default `logger` to log into console + // while saving the same log messages to an array, so we can return it in response + const arrayLogger = createArrayLogger(logger); + + fixProjectsForES(arrayLogger) + .then(() => { + arrayLogger.info('Data has been successfully fixed in DB.'); + res.status(200).json(arrayLogger.getLog()); + }) + .catch(next); + } catch (err) { + next(err); + } + }, +]; diff --git a/src/routes/index.js b/src/routes/index.js index 8421d718..275ce470 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -101,6 +101,8 @@ router.route('/v5/projects/admin/es/migrateFromDb') .patch(require('./admin/es-migrate-from-db')); router.route('/v5/projects/admin/es/fixMetadataForEs') .patch(require('./admin/es-fix-metadata-for-es')); +router.route('/v5/projects/admin/es/fixProjectsForEs') + .patch(require('./admin/es-fix-projects-for-es')); router.route('/v5/projects/admin/es/project/index') .post(require('./admin/project-index-create')); router.route('/v5/projects/admin/es/project/remove') diff --git a/src/utils/fixProjectsForES.js b/src/utils/fixProjectsForES.js new file mode 100644 index 00000000..7bade608 --- /dev/null +++ b/src/utils/fixProjectsForES.js @@ -0,0 +1,45 @@ +/* eslint-disable no-param-reassign, no-restricted-syntax, no-await-in-loop */ +/** + * Temporary script to fix project in DB to be indexed in ES + * + * Update all records in the Project table. + */ +import _ from 'lodash'; +import models from '../models'; + +/** + * Fix all projects. + * + * @param {Object} logger logger + * + * @returns {Promise} resolved when dene + */ +async function fixProjects(logger) { + const path = 'taasDefinition.team.skills'; + const projects = await models.Project.findAll(); + for (const project of projects) { + if (_.has(project, 'details')) { + const details = JSON.parse(JSON.stringify(project.details)); + const skills = _.get(details, path); + if (skills && !_.isArray(skills)) { + _.set(details, path, []); + project.details = details; + await project.save(); + logger.info(`updated record of Project with id ${project.id}`); + } + } + } +} + +/** + * Fix project model. + * + * @param {Object} logger logger + * + * @returns {undefined} + */ +async function fixProjectsForES(logger) { + await fixProjects(logger); +} + +module.exports = fixProjectsForES; From f5291e3620205fffa82bed97f70fa84f9c7b4984 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 17 Dec 2019 11:05:50 +0800 Subject: [PATCH 76/88] fix: some edge cases in fixing projects for ES in DB --- src/utils/fixProjectsForES.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/fixProjectsForES.js b/src/utils/fixProjectsForES.js index 7bade608..e6ef3dd6 100644 --- a/src/utils/fixProjectsForES.js +++ b/src/utils/fixProjectsForES.js @@ -18,10 +18,10 @@ async function fixProjects(logger) { const path = 'taasDefinition.team.skills'; const projects = await models.Project.findAll(); for (const project of projects) { - if (_.has(project, 'details')) { + if (project.details) { const details = JSON.parse(JSON.stringify(project.details)); const skills = _.get(details, path); - if (skills && !_.isArray(skills)) { + if (!_.isUndefined(skills) && !_.isArray(skills)) { _.set(details, path, []); project.details = details; await project.save(); From 5a70cae4c93e53c098f373bfec8afdfa85663b97 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 17 Dec 2019 18:04:17 +0800 Subject: [PATCH 77/88] fix: include invites for projects This issues happened during merging dev into v5 --- src/models/project.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/models/project.js b/src/models/project.js index 19a5eec9..6a9f6990 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -202,11 +202,11 @@ module.exports = function defineProject(sequelize, DataTypes) { include: [{ model: models.PhaseProduct, as: 'products', - }, { - model: models.ProjectMemberInvite, - as: 'invites', - where: { status: INVITE_STATUS.PENDING }, }], + }, { + model: models.ProjectMemberInvite, + as: 'invites', + where: { status: INVITE_STATUS.PENDING }, }], }); From 3b4aacbce92198637f14fee486e8768cd2c8e0aa Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 17 Dec 2019 18:26:40 +0800 Subject: [PATCH 78/88] fix: fallback to DB for phases LIST --- src/routes/phases/list.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/routes/phases/list.js b/src/routes/phases/list.js index 5370c2e3..cd6aaabf 100644 --- a/src/routes/phases/list.js +++ b/src/routes/phases/list.js @@ -39,7 +39,7 @@ module.exports = [ // Get project from ES return eClient.get({ index: ES_PROJECT_INDEX, type: ES_PROJECT_TYPE, id: req.params.projectId }) .then((doc) => { - req.log.debug('phase found in ES'); + req.log.debug('phases found in ES'); // Get the phases let phases = _.isArray(doc._source.phases) ? doc._source.phases : []; // eslint-disable-line no-underscore-dangle @@ -57,9 +57,19 @@ module.exports = [ }) .catch((err) => { if (err.status === 404) { - req.log.debug('No phase found in ES'); + req.log.debug('No phases found in ES'); // Load the phases - return models.Project.findByPk(projectId) + return models.Project.findByPk(projectId, { + include: [{ + model: models.ProjectPhase, + as: 'phases', + order: [['startDate', 'asc']], + include: [{ + model: models.PhaseProduct, + as: 'products', + }], + }], + }) .then((project) => { if (!project) { const apiErr = new Error(`active project not found for project id ${projectId}`); From 790d0b596ddb0a049ad7257e7e670b92a2aff008 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 17 Dec 2019 19:02:22 +0800 Subject: [PATCH 79/88] fix: project.findProjectRange - it should return all the projects, not only the one with invites - invites should be returned not only "pending", but also "requested" --- src/models/project.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/project.js b/src/models/project.js index 6a9f6990..f62a271a 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -206,7 +206,8 @@ module.exports = function defineProject(sequelize, DataTypes) { }, { model: models.ProjectMemberInvite, as: 'invites', - where: { status: INVITE_STATUS.PENDING }, + where: { status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] } }, + required: false, }], }); From 6c28808363b888cfb41a1378da429b35cc353278 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Tue, 17 Dec 2019 20:43:43 +0800 Subject: [PATCH 80/88] fix: return all members and invites from ES Without this fix, endpoints for getting a list of members and invites, return only 3 records. --- src/routes/projectMemberInvites/list.js | 9 ++++++++- src/routes/projectMembers/list.js | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/routes/projectMemberInvites/list.js b/src/routes/projectMemberInvites/list.js index b67eeae3..03edbe48 100644 --- a/src/routes/projectMemberInvites/list.js +++ b/src/routes/projectMemberInvites/list.js @@ -42,7 +42,14 @@ module.exports = [ }, }, }, - inner_hits: {}, + inner_hits: { + // TODO: replace this temporary fix with a better solution + // we have to get all the members of the project, + // should we just get a project object instead of creating such a detailed request? + // I guess just retrieving project by id and after returning members from it + // should work much faster + size: 1000, + }, }, }, }) diff --git a/src/routes/projectMembers/list.js b/src/routes/projectMembers/list.js index 25314860..92531c9d 100644 --- a/src/routes/projectMembers/list.js +++ b/src/routes/projectMembers/list.js @@ -57,7 +57,14 @@ module.exports = [ }, }, }, - inner_hits: {}, + inner_hits: { + // TODO: replace this temporary fix with a better solution + // we have to get all the members of the project, + // should we just get a project object instead of creating such a detailed request? + // I guess just retrieving project by id and after returning members from it + // should work much faster + size: 1000, + }, }, }, }) From d649ef99bba4a5a30de4740959355ce9515f2678 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 18 Dec 2019 11:05:22 +0800 Subject: [PATCH 81/88] fix: admin index endpoint include "attachments" --- src/models/project.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/models/project.js b/src/models/project.js index f62a271a..2847ab1c 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -208,6 +208,9 @@ module.exports = function defineProject(sequelize, DataTypes) { as: 'invites', where: { status: { $in: [INVITE_STATUS.PENDING, INVITE_STATUS.REQUESTED] } }, required: false, + }, { + model: models.ProjectAttachment, + as: 'attachments' }], }); From cae3b0e47468aaf426a46645b22de54572b3b986 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 18 Dec 2019 11:11:54 +0800 Subject: [PATCH 82/88] fix: lint --- src/models/project.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/project.js b/src/models/project.js index 2847ab1c..3e5c9bb3 100644 --- a/src/models/project.js +++ b/src/models/project.js @@ -210,7 +210,7 @@ module.exports = function defineProject(sequelize, DataTypes) { required: false, }, { model: models.ProjectAttachment, - as: 'attachments' + as: 'attachments', }], }); From d40aacf6062be0e4a9cd12706e53cb416e6742ce Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 18 Dec 2019 11:51:11 +0800 Subject: [PATCH 83/88] fix: delete attachements in ES --- src/routes/attachments/delete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/attachments/delete.js b/src/routes/attachments/delete.js index 2e3a821a..99dca3a1 100644 --- a/src/routes/attachments/delete.js +++ b/src/routes/attachments/delete.js @@ -61,7 +61,7 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_REMOVED, RESOURCES.ATTACHMENT, - { id: attachmentId }); + pattachment); res.status(204).json({}); }) .catch(err => next(err)); From 522ff4a4ee40b89546c9d07d4a3438b07b0321aa Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 18 Dec 2019 17:40:26 +0800 Subject: [PATCH 84/88] fix: full docker-compose Kafka events use correct Kafka event names --- local/full/kafka-client/topics.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/local/full/kafka-client/topics.txt b/local/full/kafka-client/topics.txt index 85ac3f0f..9b62bbea 100644 --- a/local/full/kafka-client/topics.txt +++ b/local/full/kafka-client/topics.txt @@ -50,6 +50,6 @@ connect.notification.project.work.update.payment connect.notification.project.work.update.progress connect.notification.project.work.update.scope connect.notification.project.workitem.update.spec -project.notification.create -project.notification.delete -project.notification.update +project.action.create +project.action.delete +project.action.update From 7a806d45ef5cfd1eecffbe9d548d71d4bd762825 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Wed, 18 Dec 2019 19:48:20 +0800 Subject: [PATCH 85/88] docs: added pecularities of v5 local setup --- README.md | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2a764cab..20fedeea 100644 --- a/README.md +++ b/README.md @@ -86,28 +86,32 @@ Microservice to manage CRUD operations for all things Projects. The project service will be served on `http://localhost:8001`. ### Import sample metadata & projects + ```bash CONNECT_USER_TOKEN= npm run demo-data ``` -This command will create sample metadata entries in the DB (duplicate what is currently in development environment). +To retrieve data from DEV env we have to provide a valid user token (`CONNECT_USER_TOKEN`). You may login to http://connect.topcoder-dev.com and find the Bearer token in the request headers using browser dev tools. -To retrieve data from DEV env we need to provide a valid user token. You may login to http://connect.topcoder-dev.com and find the Bearer token in the request headers using browser dev tools. +This command for importing data uses API to create demo data. Which has a few pecularities: +- data in DB would be for sure created +- data in ElasticSearch Index (ES) would be only created if services [project-processor-es](https://github.com/topcoder-platform/project-processor-es) and [tc-bus-api](https://github.com/topcoder-platform/tc-bus-api) are also started locally. If you don't start them, then imported data wouldn't be indexed in ES, and would be only added to DB. You may start them locally separately, or better use `local/full/docker-compose.yml` as described [next section](#local-deployment-with-other-topcoder-services) which would start them automatically. + - **NOTE** During data importing a lot of records has to be indexed in ES, so you have to wait about 5-10 minutes after `npm run demo-data` is finished until imported data is indexed in ES. You may watch logs of `project-processor-es` to see if its done or no. ### Local Deployment with other Topcoder Services. * There exists an alternate `docker-compose.yml` file that can be used to spawn containers for the following services: -| Service | Name | Port | -|----------|:-----:|:----:| -| PostGreSQL DB | db | 5432 | -| ElasticSearch | esearch | 9200,9300 | -| RabbitMQ | queue | 5672, 15672 | -| Zookeeper | zookeeper | 2181 | -| Kafka | kafka | 9092 | -| [tc-bus-api](https://github.com/topcoder-platform/tc-bus-api) | tc-bus-api | 8002 | -| [project-processor-es](https://github.com/topcoder-platform/project-processor-es) | project-processor-es | 5000 | -| [tc-notifications-api](https://github.com/topcoder-platform/tc-notifications) | tc-notifications-api | 4000 | -| [tc-notifications-processor](https://github.com/topcoder-platform/tc-notifications) | tc-notifications-processor | 4001 | + | Service | Name | Port | + |----------|:-----:|:----:| + | PostGreSQL DB | db | 5432 | + | ElasticSearch | esearch | 9200,9300 | + | RabbitMQ | queue | 5672, 15672 | + | Zookeeper | zookeeper | 2181 | + | Kafka | kafka | 9092 | + | [tc-bus-api](https://github.com/topcoder-platform/tc-bus-api) | tc-bus-api | 8002 | + | [project-processor-es](https://github.com/topcoder-platform/project-processor-es) | project-processor-es | 5000 | + | [tc-notifications-api](https://github.com/topcoder-platform/tc-notifications) | tc-notifications-api | 4000 | + | [tc-notifications-processor](https://github.com/topcoder-platform/tc-notifications) | tc-notifications-processor | 4001 | * To have kafka create a list of desired topics on startup, there exists a file with the path `local/full/kafka-client/topics.txt`. Each line from the file will be added as a topic. * To run these services simply run the following commands: @@ -134,6 +138,15 @@ To retrieve data from DEV env we need to provide a valid user token. You may log * The containers have been configured such that all Topcoder services will wait until all the topics listed in `local/full/kafka-client/topics.txt` have been created. To monitor the progress of topic creation, you can view the logs of the `kafka-client` service, which will exit when all topics have been created. +* **WARNING**
+ After all the containers are started, make sure that `project-processor-es` service started successfully, as sometimes it doesn't start successfully as Kafka wasn't yet properly started at that moment. So run `docker-compose logs -f project-processor-es` to see its logs, you should see 3 lines with text `Subscribed to project.action.` like: + ``` + project-processor-es_1 | 2019-12-18T11:10:12.849Z DEBUG no-kafka-client Subscribed to project.action.update:0 offset 0 leader 96e65c46c746:9092 + project-processor-es_1 | 2019-12-18T11:10:12.851Z DEBUG no-kafka-client Subscribed to project.action.delete:0 offset 0 leader 96e65c46c746:9092 + project-processor-es_1 | 2019-12-18T11:10:12.852Z DEBUG no-kafka-client Subscribed to project.action.create:0 offset 0 leader 96e65c46c746:9092 + ``` + If you don't see such lines, restart `project-processor-es` service ONLY by running `docker-compose restart project-processor-es`. + ### Run Connect App with Project Service locally To be able to run [Connect App](https://github.com/appirio-tech/connect-app) with the local setup of Project Service we have to do two things: From f55da524c5b288bd4bac797e5be88e8fe0afac0f Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Thu, 19 Dec 2019 12:12:40 +0800 Subject: [PATCH 86/88] fix: metadata update in ES --- src/routes/milestoneTemplates/update.js | 6 ++++-- src/routes/orgConfig/update.js | 4 +++- src/routes/productCategories/update.js | 3 ++- src/routes/productTemplates/update.js | 3 ++- src/routes/projectTemplates/update.js | 2 +- src/routes/projectTypes/update.js | 3 ++- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/routes/milestoneTemplates/update.js b/src/routes/milestoneTemplates/update.js index 3b1ce253..86d1c5e0 100644 --- a/src/routes/milestoneTemplates/update.js +++ b/src/routes/milestoneTemplates/update.js @@ -128,7 +128,8 @@ module.exports = [ req, EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED, RESOURCES.MILESTONE_TEMPLATE, - _.assign(entityToUpdate, _.pick(updated, 'id', 'updatedAt'))); + updated, + ); // emit the event for other milestone templates order updated _.map(otherUpdated, milestoneTemplate => @@ -136,7 +137,8 @@ module.exports = [ req, EVENT.ROUTING_KEY.MILESTONE_TEMPLATE_UPDATED, RESOURCES.MILESTONE_TEMPLATE, - _.assign(_.pick(milestoneTemplate.toJSON(), 'id', 'order', 'updatedBy', 'updatedAt'))), + milestoneTemplate.toJSON(), + ), ); res.json(updated); diff --git a/src/routes/orgConfig/update.js b/src/routes/orgConfig/update.js index 734a26d3..b98d909f 100644 --- a/src/routes/orgConfig/update.js +++ b/src/routes/orgConfig/update.js @@ -57,7 +57,9 @@ module.exports = [ util.sendResourceToKafkaBus(req, EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, RESOURCES.ORG_CONFIG, - entityToUpdate); + entityToUpdate, + orgConfig.get({ plain: true }), + ); res.json(orgConfig); return Promise.resolve(); }) diff --git a/src/routes/productCategories/update.js b/src/routes/productCategories/update.js index c1eceb91..f4b7611e 100644 --- a/src/routes/productCategories/update.js +++ b/src/routes/productCategories/update.js @@ -63,7 +63,8 @@ module.exports = [ util.sendResourceToKafkaBus(req, EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, RESOURCES.PRODUCT_CATEGORY, - entityToUpdate); + productCategory.get({ json: true }), + ); res.json(productCategory); return Promise.resolve(); }) diff --git a/src/routes/productTemplates/update.js b/src/routes/productTemplates/update.js index 78a4c2ec..a96c4c31 100644 --- a/src/routes/productTemplates/update.js +++ b/src/routes/productTemplates/update.js @@ -85,7 +85,8 @@ module.exports = [ util.sendResourceToKafkaBus(req, EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, RESOURCES.PRODUCT_TEMPLATE, - entityToUpdate); + productTemplate.get({ plain: true }), + ); res.json(productTemplate); return Promise.resolve(); }) diff --git a/src/routes/projectTemplates/update.js b/src/routes/projectTemplates/update.js index ef3bacb7..016a20ff 100644 --- a/src/routes/projectTemplates/update.js +++ b/src/routes/projectTemplates/update.js @@ -105,7 +105,7 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, RESOURCES.PROJECT_TEMPLATE, - entityToUpdate, + projectTemplate.get({ plain: true }), ); res.json(projectTemplate); diff --git a/src/routes/projectTypes/update.js b/src/routes/projectTypes/update.js index 3bb42b72..9cb09002 100644 --- a/src/routes/projectTypes/update.js +++ b/src/routes/projectTypes/update.js @@ -63,7 +63,8 @@ module.exports = [ req, EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, RESOURCES.PROJECT_TYPE, - entityToUpdate); + projectType.get({ plain: true }), + ); res.json(projectType); return Promise.resolve(); From 992208d04c14dd93bb3e377e20f1db582b52c4c4 Mon Sep 17 00:00:00 2001 From: Maksym Mykhailenko Date: Thu, 19 Dec 2019 12:19:35 +0800 Subject: [PATCH 87/88] fix: orgConfigs update in ES --- src/routes/orgConfig/update.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/orgConfig/update.js b/src/routes/orgConfig/update.js index b98d909f..df39c1de 100644 --- a/src/routes/orgConfig/update.js +++ b/src/routes/orgConfig/update.js @@ -57,7 +57,6 @@ module.exports = [ util.sendResourceToKafkaBus(req, EVENT.ROUTING_KEY.PROJECT_METADATA_UPDATE, RESOURCES.ORG_CONFIG, - entityToUpdate, orgConfig.get({ plain: true }), ); res.json(orgConfig); From 012de9b3e9089cee4982b98b4e7afde3ab5c7610 Mon Sep 17 00:00:00 2001 From: vikasrohit Date: Thu, 19 Dec 2019 10:56:08 +0530 Subject: [PATCH 88/88] Updated circleci config to deploy to v5 cluster Updated circleci config to deploy to new v5 cluster from now on from tc-project-service instead of projects-api. We want to keep the tc-project-service as main repo for future work because we don't want to loose the history of the api. We would deprecate the projects-api repo very soon. --- .circleci/config.yml | 76 +++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 84f1cf7c..02e01d86 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,19 +2,19 @@ version: 2 python_env: &python_env docker: - image: circleci/python:2.7-stretch-browsers - + install_awscli: &install_awscli name: "Install awscli" command: | sudo pip install awscli --upgrade install_deploysuite: &install_deploysuite - name: Installation of install_deploysuite. - command: | - git clone --branch master https://github.com/topcoder-platform/tc-deploy-scripts ../buildscript - cp ./../buildscript/master_deploy.sh . - cp ./../buildscript/buildenv.sh . - cp ./../buildscript/awsconfiguration.sh . - + name: Installation of install_deploysuite. + command: | + git clone --branch v1.4.1 https://github.com/topcoder-platform/tc-deploy-scripts ../buildscript + cp ./../buildscript/master_deploy.sh . + cp ./../buildscript/buildenv.sh . + cp ./../buildscript/awsconfiguration.sh . + # Instructions of deployment deploy_steps: &deploy_steps - checkout @@ -23,22 +23,16 @@ deploy_steps: &deploy_steps - run: *install_awscli - run: *install_deploysuite - setup_remote_docker - - run: docker build -t tc-project-service:latest . + - run: docker build -t ${APPNAME}:latest . - deploy: - name: "Running Masterscript - deploy tc-project-service " + name: "Running Masterscript - deploy tc-project-service " command: | - ./awsconfiguration.sh $DEPLOY_ENV + ./awsconfiguration.sh $DEPLOY_ENV source awsenvconf - ./buildenv.sh -e $DEPLOY_ENV -b ${VAR_ENV}-tc-project-service-deployvar + ./buildenv.sh -e $DEPLOY_ENV -b ${LOGICAL_ENV}-${APPNAME}-deployvar source buildenvvar - ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${VAR_ENV}-global-appvar,${VAR_ENV}-tc-project-service-appvar -i tc-project-service -p FARGATE + ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${LOGICAL_ENV}-global-appvar,${LOGICAL_ENV}-${APPNAME}-appvar -i ${APPNAME} - echo "======= Running Masterscript - deploy tc-project-service-consumers ===========" - if [ -e ${VAR_ENV}-tc-project-service-appvar.json ]; then sudo rm -vf ${VAR_ENV}-tc-project-service-appvar.json; fi - ./buildenv.sh -e $DEPLOY_ENV -b ${VAR_ENV}-tc-project-service-consumers-deployvar - source buildenvvar - ./master_deploy.sh -d ECS -e $DEPLOY_ENV -t latest -s ${VAR_ENV}-global-appvar,${VAR_ENV}-tc-project-service-appvar -i tc-project-service -p FARGATE - jobs: test: docker: @@ -50,13 +44,20 @@ jobs: - image: elasticsearch:2.3 - image: rabbitmq:3-management environment: - DB_MASTER_URL: postgres://circle_test:@127.0.0.1:5432/circle_test - AUTH_SECRET: secret - AUTH_DOMAIN: topcoder-dev.com - LOG_LEVEL: debug - APP_VERSION: v4 + DEPLOY_ENV: "DEV" + LOGICAL_ENV: "dev" + APPNAME: "projects-api" steps: - checkout + - run: + name: "Install dependeency" + command: | + sudo apt update + sudo apt install curl + sudo apt install python-pip + - run: *install_awscli + - run: *install_deploysuite + - setup_remote_docker - restore_cache: key: test-node-modules-{{ checksum "package.json" }} - run: npm install @@ -65,39 +66,50 @@ jobs: paths: - node_modules - run: npm run lint - - run: npm run test + - run: + name: "Running Masterscript - deploy tc-project-service " + command: | + ./awsconfiguration.sh $DEPLOY_ENV + source awsenvconf + ./buildenv.sh -e $DEPLOY_ENV -b ${LOGICAL_ENV}-${APPNAME}-testvar + source buildenvvar + npm run test + rm -f buildenvvar - run: npm run build - persist_to_workspace: root: . paths: - dist - + deployProd: <<: *python_env environment: DEPLOY_ENV: "PROD" - VAR_ENV: "prod" + LOGICAL_ENV: "prod" + APPNAME: "projects-api" steps: *deploy_steps deployDev: <<: *python_env environment: DEPLOY_ENV: "DEV" - VAR_ENV: "dev" - steps: *deploy_steps - + LOGICAL_ENV: "dev" + APPNAME: "projects-api" + steps: *deploy_steps + workflows: version: 2 build: jobs: - - test + - test: + context : org-global - deployDev: context : org-global requires: - test filters: branches: - only: ['dev', 'dev-sts', 'feature/looker-api-integration'] + only: ['develop'] - deployProd: context : org-global requires: