diff --git a/.circleci/config.yml b/.circleci/config.yml index cee39cfd..d71b5cf1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,25 +1,18 @@ version: 2 defaults: &defaults docker: - - image: docker:18.06.0-ce-git + - image: circleci/python:stretch-browsers install_dependency: &install_dependency - name: Installation of build and deployment dependencies. - command: | - apk update - apk add --no-cache bash - apk add --no-cache jq py-pip sudo curl - apk upgrade - apk add py-pip python-dev libffi-dev openssl-dev gcc libc-dev make - pip install --upgrade pip - sudo pip install awscli --upgrade - sudo curl -o /usr/local/bin/ecs-cli https://s3.amazonaws.com/amazon-ecs-cli/ecs-cli-linux-amd64-latest - sudo pip install docker-compose - sudo chmod +x /usr/local/bin/ecs-cli + name: Installation of build and deployment dependencies. + command: | + sudo apt install jq + sudo pip3 install awscli --upgrade + sudo pip3 install docker-compose install_deploysuite: &install_deploysuite name: Installation of install_deploysuite. command: | - git clone --branch v1.3 https://github.com/topcoder-platform/tc-deploy-scripts ../buildscript + 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 . @@ -79,4 +72,4 @@ workflows: context : org-global filters: branches: - only: master \ No newline at end of file + only: master diff --git a/README.md b/README.md index 30b12d50..e397e1b2 100755 --- a/README.md +++ b/README.md @@ -135,7 +135,6 @@ Please ensure to create the index `submission-test` or the index specified in th export ES_INDEX=submission-test ``` - #### Running unit tests and coverage To run unit tests alone @@ -172,6 +171,26 @@ To migrate the existing data from DynamoDB to ES, run the following script npm run db-to-es ``` +#### Store v5 challenge id for current records + +Submission API started off using the legacy challenge ids. With the v5 upgrade to the challenge api, we now need to make use of the v5 challenge ids. We have thus created a script to update existing `challengeId` attribute on submissions to v5 and store the older challenge ids in the `legacyChallengeId` attribute. + +To update the existing challengeId data on submissions in DynamoDB to v5 challengeId, set the following env variables: + +```bash +SUBMISSION_TABLE_NAME // Table name of the submission records. Defaults to 'Submission' +UPDATE_V5_CHALLENGE_BATCH_SIZE // Number of records that are updated simultaneously. Defaults to 250 +FETCH_CREATED_DATE_START // The start day of fetch latest challenges. Defaults to '2021-01-01' +FETCH_PAGE_SIZE // The page size of each api request. Defaults to 500 +``` + + +and then run the following script + +``` +npm run update-to-v5-challengeId +``` + #### Swagger UI Swagger UI will be served at `http://localhost:3000/docs` diff --git a/config/default.js b/config/default.js index aaf4412b..fa8090f6 100755 --- a/config/default.js +++ b/config/default.js @@ -21,8 +21,8 @@ module.exports = { BUSAPI_URL: process.env.BUSAPI_URL || 'https://api.topcoder-dev.com/v5', KAFKA_ERROR_TOPIC: process.env.KAFKA_ERROR_TOPIC || 'error.notification', KAFKA_AGGREGATE_TOPIC: process.env.KAFKA_AGGREGATE_TOPIC || 'submission.notification.aggregate', - CHALLENGEAPI_URL: process.env.CHALLENGEAPI_URL || 'https://api.topcoder-dev.com/v4/challenges', CHALLENGEAPI_V5_URL: process.env.CHALLENGEAPI_V5_URL || 'https://api.topcoder-dev.com/v5/challenges', + RESOURCEAPI_V5_BASE_URL: process.env.RESOURCEAPI_V5_BASE_URL || 'https://api.topcoder-dev.com/v5', AUTH0_URL: process.env.AUTH0_URL, // Auth0 credentials for Submission Service AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE || 'https://www.topcoder.com', TOKEN_CACHE_TIME: process.env.TOKEN_CACHE_TIME, @@ -37,5 +37,9 @@ module.exports = { PAGE_SIZE: process.env.PAGE_SIZE || 20, MAX_PAGE_SIZE: parseInt(process.env.MAX_PAGE_SIZE) || 100, ES_BATCH_SIZE: process.env.ES_BATCH_SIZE || 250, - AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL + UPDATE_V5_CHALLENGE_BATCH_SIZE: process.env.UPDATE_V5_CHALLENGE_BATCH_SIZE || 100, + SUBMISSION_TABLE_NAME: process.env.SUBMISSION_TABLE_NAME || 'Submission', + AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL, + FETCH_CREATED_DATE_START: process.env.FETCH_CREATED_DATE_START || '2021-01-01', + FETCH_PAGE_SIZE: process.env.FETCH_PAGE_SIZE || 500 } diff --git a/config/test.js b/config/test.js index c702b1f7..4b9a3680 100644 --- a/config/test.js +++ b/config/test.js @@ -7,7 +7,7 @@ module.exports = { LOG_LEVEL: 'info', WEB_SERVER_PORT: 3010, AUTH_SECRET: 'mysecret', - VALID_ISSUERS: '["https://api.topcoder.com"]', + VALID_ISSUERS: process.env.VALID_ISSUERS ? process.env.VALID_ISSUERS.replace(/\\"/g, '') : '["https://api.topcoder.com","https://topcoder-dev.auth0.com/"]', API_VERSION: process.env.API_VERSION || '/api/v5', aws: { AWS_REGION: process.env.AWS_REGION || 'us-east-1', // AWS Region to be used by the application @@ -16,14 +16,14 @@ module.exports = { S3_BUCKET: process.env.S3_BUCKET_TEST || 'tc-testing-submissions' // S3 Bucket to which submissions need to be uploaded }, BUSAPI_EVENTS_URL: 'https://api.topcoder-dev.com/v5/bus/events', - CHALLENGEAPI_URL: 'https://api.topcoder-dev.com/v4/challenges', + BUSAPI_URL: 'https://api.topcoder-dev.com/v5', + CHALLENGEAPI_V5_URL: 'https://api.topcoder-dev.com/v5/challenges', esConfig: { ES_INDEX: process.env.ES_INDEX_TEST || 'submission-test', ES_TYPE: process.env.ES_TYPE_TEST || '_doc' // ES 6.x accepts only 1 Type per index and it's mandatory to define it }, AUTH0_URL: process.env.AUTH0_URL, // Auth0 credentials for Submission Service - AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE || 'https://www.topcoder.com', - TOKEN_CACHE_TIME: process.env.TOKEN_CACHE_TIME, + AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE, AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET, USER_TOKEN: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLmNvbSIsImhhbmRsZSI6IlNoYXJhdGhrdW1hcjkyIiwiZXhwIjo1NTUzMDE5OTI1OSwidXNlcklkIjoiNDA0OTMwNTAiLCJpYXQiOjE1MzAxOTg2NTksImVtYWlsIjoiU2hhcmF0aGt1bWFyOTJAdG9wY29kZXIuY29tIiwianRpIjoiYzNhYzYwOGEtNTZiZS00NWQwLThmNmEtMzFmZTk0Yjk1NjFjIn0.2gtNJwhcv7MYc-muX3Nv-B0RdWbhMRl7-xrwFUsLazM', diff --git a/docs/swagger.yaml b/docs/swagger.yaml index f41a9d40..812af77a 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -95,6 +95,7 @@ paths: legacySubmissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0502' legacyUploadId: 'a12a4180-65aa-42ec-a945-5fd21dec0502' submissionPhaseId: 764567 + submittedDate: '2018-05-20T07:00:30.123Z' created: '2018-05-20T07:00:30.123Z' updated: '2018-06-01T07:36:28.178Z' createdBy: 'topcoder user' @@ -107,6 +108,7 @@ paths: legacySubmissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0502' legacyUploadId: 'a12a4180-65aa-42ec-a945-5fd21dec0502' submissionPhaseId: 764567 + submittedDate: '2018-05-20T08:00:30.000Z' created: '2018-05-20T08:00:30.000Z' updated: '2018-06-01T09:23:00.178Z' createdBy: 'topcoder user' @@ -205,6 +207,7 @@ paths: Create a new submission. **Authorization:** Submission creation is accessible by roles `topcoder user`, `admin` and `copilot`. + **Note** Value for `submittedDate` attribute can only be provided by users with `admin` role tags: - Submissions operationId: createSubmission @@ -262,6 +265,11 @@ paths: name: submissionPhaseId type: integer description: Submission Phase Id + - in: formData + name: submittedDate + type: string + format: date-time + description: Date of submission (defaults to submission creation date if none passed) responses: 201: description: Created - The request was successful and the resource is returned. @@ -612,6 +620,7 @@ paths: scoreCardId: 123456789 isPassing: false isFinal: false + reviewedDate: '2018-05-20T07:00:30.123Z' created: '2018-05-20T07:00:30.123Z' updated: '2018-06-01T07:36:28.178Z' createdBy: copilot @@ -622,6 +631,7 @@ paths: scoreCardId: 123456789 isPassing: true isFinal: true + reviewedDate: '2018-05-20T07:00:30.123Z' created: '2018-05-20T07:00:30.123Z' updated: '2018-06-01T07:36:28.178Z' createdBy: copilot @@ -718,6 +728,8 @@ paths: Create a new review summation. **Authorization:** Review summation creation is accessible by roles `admin` and `copilot`. + + **Note** Value for `reviewedDate` attribute can only be provided by users with `admin` role tags: - 'Review summations' operationId: createReviewSummation @@ -931,6 +943,7 @@ paths: scoreCardId: 123456789 submissionId: 'd67a4180-65aa-42ec-a945-5fd21dec0503' status: 'queued' + reviewedDate: '2018-05-20T07:00:30.123Z' created: '2018-05-20T07:00:30.123Z' updated: '2018-06-01T07:36:28.178Z' createdBy: 'admin' @@ -942,6 +955,7 @@ paths: scoreCardId: 123456789 submissionId: 'd23a4180-65aa-42ec-a945-5fd21dec0503' status: 'completed' + reviewedDate: '2018-05-20T07:00:30.123Z' created: '2018-05-20T07:00:30.123Z' updated: '2018-06-01T07:36:28.178Z' createdBy: 'admin' @@ -1038,6 +1052,8 @@ paths: Create a new review. **Authorization:** Review creation is accessible by roles `admin` and `copilot`. + + **Note** Value for `reviewedDate` attribute can only be provided by users with `admin` role tags: - 'Reviews' operationId: createReview @@ -1880,6 +1896,11 @@ definitions: type: integer description: The submission phase id. example: '5dea6d9e-161a-4c7a-b316-597c73a7b8f4' + submittedDate: + type: string + format: date-time + description: Date of submission (defaults to submission creation date if none passed) + example: '2018-05-20T07:00:30.123Z' UpdatableSubmission: description: The submission entity fields that updates whole entity. @@ -1939,6 +1960,11 @@ definitions: metadata: type: object description: Review summation metadata in JSON format + reviewedDate: + type: string + format: date-time + description: Date of review summation (defaults to review summation creation date if none passed) + example: '2018-05-20T07:00:30.123Z' UpdatableReviewSummation: description: The review summation entity fields that updates whole entity. @@ -2006,6 +2032,11 @@ definitions: metadata: type: object description: Review Metadata in JSON format + reviewedDate: + type: string + format: date-time + description: Date of review (defaults to review creation date if none passed) + example: '2018-05-20T07:00:30.123Z' UpdatableReview: description: The review entity fields that updates whole entity. diff --git a/package-lock.json b/package-lock.json old mode 100755 new mode 100644 index fac7153a..628b94d1 --- a/package-lock.json +++ b/package-lock.json @@ -671,6 +671,14 @@ "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "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" + } + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -1009,561 +1017,6 @@ "upath": "^1.1.1" }, "dependencies": { - "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 - } - } - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2727,6 +2180,11 @@ "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==" }, + "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==" + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -2880,6 +2338,17 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4342,6 +3811,15 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "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", @@ -4544,8 +4022,7 @@ "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "optional": true + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, "nanomatch": { "version": "1.2.9", @@ -4742,7 +4219,8 @@ "dependencies": { "align-text": { "version": "0.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "optional": true, "requires": { @@ -4753,17 +4231,20 @@ }, "amdefine": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, "ansi-regex": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "append-transform": { "version": "0.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", "dev": true, "requires": { "default-require-extensions": "^1.0.0" @@ -4771,57 +4252,68 @@ }, "archy": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, "arr-diff": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", "dev": true }, "arr-flatten": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "arr-union": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, "array-unique": { "version": "0.3.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, "arrify": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, "assign-symbols": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, "async": { "version": "1.5.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, "atob": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", "dev": true }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "base": { "version": "0.11.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -4835,7 +4327,8 @@ "dependencies": { "define-property": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { "is-descriptor": "^1.0.0" @@ -4843,7 +4336,8 @@ }, "is-accessor-descriptor": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4851,7 +4345,8 @@ }, "is-data-descriptor": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4859,7 +4354,8 @@ }, "is-descriptor": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -4869,14 +4365,16 @@ }, "kind-of": { "version": "6.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -4885,7 +4383,8 @@ }, "braces": { "version": "2.3.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", "dev": true, "requires": { "arr-flatten": "^1.1.0", @@ -4902,7 +4401,8 @@ "dependencies": { "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -4912,12 +4412,14 @@ }, "builtin-modules": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, "cache-base": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -4933,7 +4435,8 @@ }, "caching-transform": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-bb2y8g+Nj7znnz6U6dF0Lc31wKE=", "dev": true, "requires": { "md5-hex": "^1.2.0", @@ -4943,13 +4446,15 @@ }, "camelcase": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", "dev": true, "optional": true }, "center-align": { "version": "0.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "dev": true, "optional": true, "requires": { @@ -4959,7 +4464,8 @@ }, "class-utils": { "version": "0.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -4970,7 +4476,8 @@ "dependencies": { "define-property": { "version": "0.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { "is-descriptor": "^0.1.0" @@ -4980,7 +4487,8 @@ }, "cliui": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "dev": true, "optional": true, "requires": { @@ -4991,7 +4499,8 @@ "dependencies": { "wordwrap": { "version": "0.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", "dev": true, "optional": true } @@ -4999,12 +4508,14 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "collection-visit": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { "map-visit": "^1.0.0", @@ -5013,32 +4524,38 @@ }, "commondir": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "component-emitter": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", "dev": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "convert-source-map": { "version": "1.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", "dev": true }, "copy-descriptor": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, "cross-spawn": { "version": "4.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", "dev": true, "requires": { "lru-cache": "^4.0.1", @@ -5047,7 +4564,8 @@ }, "debug": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -5055,22 +4573,26 @@ }, "debug-log": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", "dev": true }, "decamelize": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "decode-uri-component": { "version": "0.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, "default-require-extensions": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", "dev": true, "requires": { "strip-bom": "^2.0.0" @@ -5078,7 +4600,8 @@ }, "define-property": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -5087,7 +4610,8 @@ "dependencies": { "is-accessor-descriptor": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -5095,7 +4619,8 @@ }, "is-data-descriptor": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -5103,7 +4628,8 @@ }, "is-descriptor": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -5113,14 +4639,16 @@ }, "kind-of": { "version": "6.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } }, "error-ex": { "version": "1.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -5128,7 +4656,8 @@ }, "execa": { "version": "0.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { "cross-spawn": "^5.0.1", @@ -5142,7 +4671,8 @@ "dependencies": { "cross-spawn": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { "lru-cache": "^4.0.1", @@ -5154,7 +4684,8 @@ }, "expand-brackets": { "version": "2.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { "debug": "^2.3.3", @@ -5168,7 +4699,8 @@ "dependencies": { "debug": { "version": "2.6.9", - "bundled": true, + "resolved": false, + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -5176,7 +4708,8 @@ }, "define-property": { "version": "0.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { "is-descriptor": "^0.1.0" @@ -5184,7 +4717,8 @@ }, "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -5194,7 +4728,8 @@ }, "extend-shallow": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { "assign-symbols": "^1.0.0", @@ -5203,7 +4738,8 @@ "dependencies": { "is-extendable": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -5213,7 +4749,8 @@ }, "extglob": { "version": "2.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -5228,7 +4765,8 @@ "dependencies": { "define-property": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { "is-descriptor": "^1.0.0" @@ -5236,7 +4774,8 @@ }, "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -5244,7 +4783,8 @@ }, "is-accessor-descriptor": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -5252,7 +4792,8 @@ }, "is-data-descriptor": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -5260,7 +4801,8 @@ }, "is-descriptor": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -5270,14 +4812,16 @@ }, "kind-of": { "version": "6.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } }, "fill-range": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -5288,7 +4832,8 @@ "dependencies": { "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -5298,7 +4843,8 @@ }, "find-cache-dir": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", "dev": true, "requires": { "commondir": "^1.0.1", @@ -5308,7 +4854,8 @@ }, "find-up": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { "locate-path": "^2.0.0" @@ -5316,12 +4863,14 @@ }, "for-in": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, "foreground-child": { "version": "1.5.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", "dev": true, "requires": { "cross-spawn": "^4", @@ -5330,7 +4879,8 @@ }, "fragment-cache": { "version": "0.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { "map-cache": "^0.2.2" @@ -5338,27 +4888,32 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "get-caller-file": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", "dev": true }, "get-stream": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, "get-value": { "version": "2.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, "glob": { "version": "7.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -5371,12 +4926,14 @@ }, "graceful-fs": { "version": "4.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, "handlebars": { "version": "4.0.11", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { "async": "^1.4.0", @@ -5387,7 +4944,8 @@ "dependencies": { "source-map": { "version": "0.4.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { "amdefine": ">=0.0.4" @@ -5397,7 +4955,8 @@ }, "has-value": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { "get-value": "^2.0.6", @@ -5407,7 +4966,8 @@ }, "has-values": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { "is-number": "^3.0.0", @@ -5416,7 +4976,8 @@ "dependencies": { "kind-of": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { "is-buffer": "^1.1.5" @@ -5426,17 +4987,20 @@ }, "hosted-git-info": { "version": "2.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-IyNbKasjDFdqqw1PE/wEawsDgiI=", "dev": true }, "imurmurhash": { "version": "0.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", @@ -5445,17 +5009,20 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "invert-kv": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, "is-accessor-descriptor": { "version": "0.1.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { "kind-of": "^3.0.2" @@ -5463,17 +5030,20 @@ }, "is-arrayish": { "version": "0.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "is-buffer": { "version": "1.1.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", "dev": true }, "is-builtin-module": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { "builtin-modules": "^1.0.0" @@ -5481,7 +5051,8 @@ }, "is-data-descriptor": { "version": "0.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { "kind-of": "^3.0.2" @@ -5489,7 +5060,8 @@ }, "is-descriptor": { "version": "0.1.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -5499,24 +5071,28 @@ "dependencies": { "kind-of": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } }, "is-extendable": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "is-number": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { "kind-of": "^3.0.2" @@ -5524,7 +5100,8 @@ }, "is-odd": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-dkZiRnH9fqVYzNmieVGC8pWPGyQ=", "dev": true, "requires": { "is-number": "^4.0.0" @@ -5532,14 +5109,16 @@ "dependencies": { "is-number": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", "dev": true } } }, "is-plain-object": { "version": "2.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", "dev": true, "requires": { "isobject": "^3.0.1" @@ -5547,42 +5126,50 @@ }, "is-stream": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, "is-utf8": { "version": "0.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, "is-windows": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", "dev": true }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isexe": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isobject": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, "istanbul-lib-coverage": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-99jy5CuX43/nlhFMsPnWi146Q0E=", "dev": true }, "istanbul-lib-hook": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hTjZcDcss3FtU+VVI91UtVeo2Js=", "dev": true, "requires": { "append-transform": "^0.4.0" @@ -5590,7 +5177,8 @@ }, "istanbul-lib-report": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-LfEhiMD6d5kMDSF20tC6M5QYglk=", "dev": true, "requires": { "istanbul-lib-coverage": "^1.1.2", @@ -5601,12 +5189,14 @@ "dependencies": { "has-flag": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, "supports-color": { "version": "3.2.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { "has-flag": "^1.0.0" @@ -5616,7 +5206,8 @@ }, "istanbul-lib-source-maps": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-/+a+Tnq4bTYD5CkNVJkLFFBvybE=", "dev": true, "requires": { "debug": "^3.1.0", @@ -5628,7 +5219,8 @@ }, "istanbul-reports": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ty6OkoqnoF0dpsQn1AmLJlXsczQ=", "dev": true, "requires": { "handlebars": "^4.0.3" @@ -5636,7 +5228,8 @@ }, "kind-of": { "version": "3.2.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { "is-buffer": "^1.1.5" @@ -5644,13 +5237,15 @@ }, "lazy-cache": { "version": "1.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", "dev": true, "optional": true }, "lcid": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { "invert-kv": "^1.0.0" @@ -5658,7 +5253,8 @@ }, "load-json-file": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -5670,7 +5266,8 @@ }, "locate-path": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { "p-locate": "^2.0.0", @@ -5679,20 +5276,23 @@ "dependencies": { "path-exists": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true } } }, "longest": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "dev": true, "optional": true }, "lru-cache": { "version": "4.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-oRdc80lt/IQ2wVbDNLSVWZK85pw=", "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -5701,12 +5301,14 @@ }, "map-cache": { "version": "0.2.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, "map-visit": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { "object-visit": "^1.0.0" @@ -5714,7 +5316,8 @@ }, "md5-hex": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-0sSv6YPENwZiF5uMrRRSGRNQRsQ=", "dev": true, "requires": { "md5-o-matic": "^0.1.1" @@ -5722,12 +5325,14 @@ }, "md5-o-matic": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=", "dev": true }, "mem": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { "mimic-fn": "^1.0.0" @@ -5735,7 +5340,8 @@ }, "merge-source-map": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-L93n5gIJOfcJBqaPLXrmheTIxkY=", "dev": true, "requires": { "source-map": "^0.6.1" @@ -5743,14 +5349,16 @@ "dependencies": { "source-map": { "version": "0.6.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } }, "micromatch": { "version": "3.1.10", - "bundled": true, + "resolved": false, + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -5770,19 +5378,22 @@ "dependencies": { "kind-of": { "version": "6.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } }, "mimic-fn": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", "dev": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -5790,12 +5401,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mixin-deep": { "version": "1.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-pJ5yaNzhoNlpjkUybFYm3zVD0P4=", "dev": true, "requires": { "for-in": "^1.0.2", @@ -5804,7 +5417,8 @@ "dependencies": { "is-extendable": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -5814,7 +5428,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -5822,12 +5437,14 @@ }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "nanomatch": { "version": "1.2.9", - "bundled": true, + "resolved": false, + "integrity": "sha1-h59xUMstq3pHElkGbBBO7m4Pp8I=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -5846,14 +5463,16 @@ "dependencies": { "kind-of": { "version": "6.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } }, "normalize-package-data": { "version": "2.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "dev": true, "requires": { "hosted-git-info": "^2.1.4", @@ -5864,7 +5483,8 @@ }, "npm-run-path": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { "path-key": "^2.0.0" @@ -5872,17 +5492,20 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, "object-copy": { "version": "0.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { "copy-descriptor": "^0.1.0", @@ -5892,7 +5515,8 @@ "dependencies": { "define-property": { "version": "0.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { "is-descriptor": "^0.1.0" @@ -5902,7 +5526,8 @@ }, "object-visit": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { "isobject": "^3.0.0" @@ -5910,7 +5535,8 @@ }, "object.pick": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { "isobject": "^3.0.1" @@ -5918,7 +5544,8 @@ }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -5926,7 +5553,8 @@ }, "optimist": { "version": "0.6.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { "minimist": "~0.0.1", @@ -5935,12 +5563,14 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, "os-locale": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-QrwpAKa1uL0XN2yOiCtlr8zyS/I=", "dev": true, "requires": { "execa": "^0.7.0", @@ -5950,12 +5580,14 @@ }, "p-finally": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, "p-limit": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DpK2vty1nwIsE9DxlJ3ILRWQnxw=", "dev": true, "requires": { "p-try": "^1.0.0" @@ -5963,7 +5595,8 @@ }, "p-locate": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { "p-limit": "^1.1.0" @@ -5971,12 +5604,14 @@ }, "p-try": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "parse-json": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { "error-ex": "^1.2.0" @@ -5984,12 +5619,14 @@ }, "pascalcase": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, "path-exists": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { "pinkie-promise": "^2.0.0" @@ -5997,22 +5634,26 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-key": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "path-parse": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, "path-type": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -6022,17 +5663,20 @@ }, "pify": { "version": "2.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, "pinkie": { "version": "2.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", "dev": true }, "pinkie-promise": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { "pinkie": "^2.0.0" @@ -6040,7 +5684,8 @@ }, "pkg-dir": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", "dev": true, "requires": { "find-up": "^1.0.0" @@ -6048,7 +5693,8 @@ "dependencies": { "find-up": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { "path-exists": "^2.0.0", @@ -6059,17 +5705,20 @@ }, "posix-character-classes": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, "pseudomap": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, "read-pkg": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { "load-json-file": "^1.0.0", @@ -6079,7 +5728,8 @@ }, "read-pkg-up": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { "find-up": "^1.0.0", @@ -6088,7 +5738,8 @@ "dependencies": { "find-up": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { "path-exists": "^2.0.0", @@ -6099,7 +5750,8 @@ }, "regex-not": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", "dev": true, "requires": { "extend-shallow": "^3.0.2", @@ -6108,42 +5760,50 @@ }, "repeat-element": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", "dev": true }, "repeat-string": { "version": "1.6.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, "require-directory": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-main-filename": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, "resolve-from": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", "dev": true }, "resolve-url": { "version": "0.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, "ret": { "version": "0.1.15", - "bundled": true, + "resolved": false, + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", "dev": true }, "right-align": { "version": "0.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "dev": true, "optional": true, "requires": { @@ -6152,7 +5812,8 @@ }, "rimraf": { "version": "2.6.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", "dev": true, "requires": { "glob": "^7.0.5" @@ -6160,7 +5821,8 @@ }, "safe-regex": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { "ret": "~0.1.10" @@ -6168,17 +5830,20 @@ }, "semver": { "version": "5.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs=", "dev": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "set-value": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ca5KiPD+77v1LR6mBPP7MV67YnQ=", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -6189,7 +5854,8 @@ "dependencies": { "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -6199,7 +5865,8 @@ }, "shebang-command": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -6207,22 +5874,26 @@ }, "shebang-regex": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, "slide": { "version": "1.1.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", "dev": true }, "snapdragon": { "version": "0.8.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", "dev": true, "requires": { "base": "^0.11.1", @@ -6237,7 +5908,8 @@ "dependencies": { "debug": { "version": "2.6.9", - "bundled": true, + "resolved": false, + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -6245,7 +5917,8 @@ }, "define-property": { "version": "0.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { "is-descriptor": "^0.1.0" @@ -6253,7 +5926,8 @@ }, "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -6263,7 +5937,8 @@ }, "snapdragon-node": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", "dev": true, "requires": { "define-property": "^1.0.0", @@ -6273,7 +5948,8 @@ "dependencies": { "define-property": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { "is-descriptor": "^1.0.0" @@ -6281,7 +5957,8 @@ }, "is-accessor-descriptor": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -6289,7 +5966,8 @@ }, "is-data-descriptor": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -6297,7 +5975,8 @@ }, "is-descriptor": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -6307,14 +5986,16 @@ }, "kind-of": { "version": "6.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } }, "snapdragon-util": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", "dev": true, "requires": { "kind-of": "^3.2.0" @@ -6322,12 +6003,14 @@ }, "source-map": { "version": "0.5.7", - "bundled": true, + "resolved": false, + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "source-map-resolve": { "version": "0.5.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=", "dev": true, "requires": { "atob": "^2.1.1", @@ -6339,12 +6022,14 @@ }, "source-map-url": { "version": "0.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, "spawn-wrap": { "version": "1.4.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-z/WOc6giRhe2Vhq9wyWG6gyCJIw=", "dev": true, "requires": { "foreground-child": "^1.5.6", @@ -6357,7 +6042,8 @@ }, "spdx-correct": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BaW01xU6GVvJLDxCW2nzsqlSTII=", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -6366,12 +6052,14 @@ }, "spdx-exceptions": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-LHrmEFbHFKW5ubKyr30xHvXHj+k=", "dev": true }, "spdx-expression-parse": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -6380,12 +6068,14 @@ }, "spdx-license-ids": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-enzShHDMbToc/m1miG9rxDDTrIc=", "dev": true }, "split-string": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", "dev": true, "requires": { "extend-shallow": "^3.0.0" @@ -6393,7 +6083,8 @@ }, "static-extend": { "version": "0.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { "define-property": "^0.2.5", @@ -6402,7 +6093,8 @@ "dependencies": { "define-property": { "version": "0.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { "is-descriptor": "^0.1.0" @@ -6412,7 +6104,8 @@ }, "string-width": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -6421,7 +6114,8 @@ }, "strip-ansi": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -6429,7 +6123,8 @@ }, "strip-bom": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { "is-utf8": "^0.2.0" @@ -6437,12 +6132,14 @@ }, "strip-eof": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, "test-exclude": { "version": "4.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-36Ii8DSAvKaSB8pyizfXS0X3JPo=", "dev": true, "requires": { "arrify": "^1.0.1", @@ -6454,7 +6151,8 @@ }, "to-object-path": { "version": "0.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { "kind-of": "^3.0.2" @@ -6462,7 +6160,8 @@ }, "to-regex": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", "dev": true, "requires": { "define-property": "^2.0.2", @@ -6473,7 +6172,8 @@ }, "to-regex-range": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { "is-number": "^3.0.0", @@ -6482,7 +6182,8 @@ }, "uglify-js": { "version": "2.8.29", - "bundled": true, + "resolved": false, + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "dev": true, "optional": true, "requires": { @@ -6493,7 +6194,8 @@ "dependencies": { "yargs": { "version": "3.10.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "optional": true, "requires": { @@ -6507,13 +6209,15 @@ }, "uglify-to-browserify": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", "dev": true, "optional": true }, "union-value": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -6524,7 +6228,8 @@ "dependencies": { "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -6532,7 +6237,8 @@ }, "set-value": { "version": "0.4.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -6545,7 +6251,8 @@ }, "unset-value": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { "has-value": "^0.3.1", @@ -6554,7 +6261,8 @@ "dependencies": { "has-value": { "version": "0.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { "get-value": "^2.0.3", @@ -6564,7 +6272,8 @@ "dependencies": { "isobject": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, "requires": { "isarray": "1.0.0" @@ -6574,19 +6283,22 @@ }, "has-values": { "version": "0.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", "dev": true } } }, "urix": { "version": "0.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, "use": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FHFr8D/f79AwQK71jYtLhfOnxUQ=", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -6594,14 +6306,16 @@ "dependencies": { "kind-of": { "version": "6.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true } } }, "validate-npm-package-license": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-gWQ7y+8b3+zUYjeT3EZIlIupgzg=", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -6610,7 +6324,8 @@ }, "which": { "version": "1.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", "dev": true, "requires": { "isexe": "^2.0.0" @@ -6618,23 +6333,27 @@ }, "which-module": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "window-size": { "version": "0.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", "dev": true, "optional": true }, "wordwrap": { "version": "0.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, "wrap-ansi": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { "string-width": "^1.0.1", @@ -6643,12 +6362,14 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -6656,7 +6377,8 @@ }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -6666,7 +6388,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -6676,12 +6399,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "write-file-atomic": { "version": "1.3.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -6691,17 +6416,20 @@ }, "y18n": { "version": "3.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "dev": true }, "yallist": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, "yargs": { "version": "11.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-kLhpk07W6HERXqL/WLA/RyTtLXc=", "dev": true, "requires": { "cliui": "^4.0.0", @@ -6720,12 +6448,14 @@ "dependencies": { "camelcase": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, "cliui": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-NIQi2+gtgAswIu709qwQvy5NG0k=", "dev": true, "requires": { "string-width": "^2.1.1", @@ -6735,7 +6465,8 @@ }, "yargs-parser": { "version": "9.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { "camelcase": "^4.1.0" @@ -6745,7 +6476,8 @@ }, "yargs-parser": { "version": "8.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-8TdqM7Ziml0GN4KUTacyYx6WaVA=", "dev": true, "requires": { "camelcase": "^4.1.0" @@ -6753,7 +6485,8 @@ "dependencies": { "camelcase": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true } } diff --git a/package.json b/package.json index b09e8038..9a75b5da 100755 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "delete-index": "node scripts/deleteIndex.js", "init-es": "node scripts/loadES.js", "db-to-es": "node scripts/migrateFromDBToES.js", + "update-to-v5-challengeId": "node scripts/updateToV5ChallengeId.js", + "update-to-v5-challengeId-v2": "node scripts/updateToV5ChallengeIdV2.js", "test": "mocha test/unit/*.test.js --require test/unit/prepare.js --exit", "e2e": "mocha test/e2e/*.test.js --require test/e2e/prepare.js --exit", "cov": "nyc --reporter=html --reporter=text mocha test/unit/*.test.js --require test/unit/prepare.js --exit", diff --git a/scripts/createIndex.js b/scripts/createIndex.js index e847fb3d..2d1f1208 100644 --- a/scripts/createIndex.js +++ b/scripts/createIndex.js @@ -17,7 +17,8 @@ co(function * createIndex () { // fields not specified below will be 'text' by default properties: { resource: { type: 'keyword' }, - challengeId: { type: 'long' }, + challengeId: { type: 'keyword' }, + legacyChallengeId: { type: 'keyword' }, memberId: { type: 'keyword' }, type: { type: 'keyword' }, isFileSubmission: { type: 'boolean' }, @@ -32,7 +33,7 @@ co(function * createIndex () { aggregateScore: { type: 'float' }, isPassing: { type: 'boolean' }, legacySubmissionId: { type: 'keyword' }, - submissionPhaseId: { type: 'long' }, + submissionPhaseId: { type: 'keyword' }, fileType: { type: 'keyword' }, filename: { type: 'keyword' }, review: { type: 'nested' }, diff --git a/scripts/data/Reviews.json b/scripts/data/Reviews.json index 40b4e5de..38a3beaa 100644 --- a/scripts/data/Reviews.json +++ b/scripts/data/Reviews.json @@ -49,7 +49,7 @@ }, { "id": "d24d4180-65aa-42ec-a945-5fd21dec0505", - "score": 100, + "score": 100.0, "typeId": "f28b2725-ef90-4495-af59-ceb2bd98fc10", "reviewerId": "c23a4180-65aa-42ec-a945-5fd21dec0503", "scoreCardId": 123456789, @@ -85,7 +85,7 @@ }, { "id": "d24d4180-65aa-42ec-a945-5fd21dec0508", - "score": 100, + "score": 100.0, "typeId": "f28b2725-ef90-4495-af59-ceb2bd98fc10", "reviewerId": "c23a4180-65aa-42ec-a945-5fd21dec0503", "scoreCardId": 123456789, diff --git a/scripts/data/Submissions.json b/scripts/data/Submissions.json index 4d9e6bc1..15654504 100644 --- a/scripts/data/Submissions.json +++ b/scripts/data/Submissions.json @@ -109,7 +109,7 @@ }, { "id": "d24d4180-65aa-42ec-a945-5fd21dec0505", - "score": 100, + "score": 100.0, "typeId": "f28b2725-ef90-4495-af59-ceb2bd98fc10", "reviewerId": "c23a4180-65aa-42ec-a945-5fd21dec0503", "scoreCardId": 123456789, @@ -183,7 +183,7 @@ "review": [ { "id": "d24d4180-65aa-42ec-a945-5fd21dec0508", - "score": 100, + "score": 100.0, "typeId": "f28b2725-ef90-4495-af59-ceb2bd98fc10", "reviewerId": "c23a4180-65aa-42ec-a945-5fd21dec0503", "scoreCardId": 123456789, diff --git a/scripts/migrateFromDBToES.js b/scripts/migrateFromDBToES.js index 283defe6..8d6ff6b1 100644 --- a/scripts/migrateFromDBToES.js +++ b/scripts/migrateFromDBToES.js @@ -17,7 +17,7 @@ const esClient = helper.getEsClient() * @returns {Promise} */ function * migrateRecords (tableName) { - let promises = [] + let body = [] let batchCounter = 1 const params = { TableName: tableName @@ -25,32 +25,43 @@ function * migrateRecords (tableName) { // Process until all the records from DB is fetched while (true) { const records = yield dbhelper.scanRecords(params) - const totalRecords = records.Items.length - logger.debug(`Number of ${tableName}s fetched from DB - ` + totalRecords) - for (let i = 0; i < totalRecords; i++) { - const record = { - index: config.get('esConfig.ES_INDEX'), - type: config.get('esConfig.ES_TYPE'), - id: records.Items[i].id, - body: { - doc: _.extend({ resource: helper.camelize(tableName) }, records.Items[i]), - doc_as_upsert: true + logger.debug(`Number of ${tableName}s currently fetched from DB - ` + records.Items.length) + let i = 0 + for (const item of records.Items) { + // action + body.push({ + index: { + _id: item.id } - } - promises.push(esClient.update(record)) + }) + // data + body.push(_.extend({ resource: helper.camelize(tableName) }, item)) if (i % config.ES_BATCH_SIZE === 0) { logger.debug(`${tableName} - Processing batch # ` + batchCounter) - yield promises - promises = [] + yield esClient.bulk({ + index: config.get('esConfig.ES_INDEX'), + type: config.get('esConfig.ES_TYPE'), + body + }) + body = [] batchCounter++ } + i++ } // Continue fetching the remaining records from Database if (typeof records.LastEvaluatedKey !== 'undefined') { params.ExclusiveStartKey = records.LastEvaluatedKey } else { + if (body.length > 0) { + logger.debug(`${tableName} - Final batch processing...`) + yield esClient.bulk({ + index: config.get('esConfig.ES_INDEX'), + type: config.get('esConfig.ES_TYPE'), + body + }) + } break // If there are no more records to process, exit the loop } } diff --git a/scripts/updateToV5ChallengeId.js b/scripts/updateToV5ChallengeId.js new file mode 100644 index 00000000..617f17f4 --- /dev/null +++ b/scripts/updateToV5ChallengeId.js @@ -0,0 +1,93 @@ +/** + * Store v5 challenge id for current records + */ + +const _ = require('lodash') +const co = require('co') +const config = require('config') +const logger = require('../src/common/logger') +const dbhelper = require('../src/common/dbhelper') +const helper = require('../src/common/helper') + +/** + * Update Submission's challenge id to v5 + * @param {Object} submission The submission record + * @param {Array} failedContainer The failed records container + * @param {String} v5challengeId The v5 challenge id + * @returns {Promise} + */ +function * updateRecord (submission, failedContainer, v5challengeId) { + const record = { + TableName: 'Submission', + Key: { + id: submission.id + }, + UpdateExpression: `set challengeId = :c, legacyChallengeId = :l`, + ExpressionAttributeValues: { + ':c': v5challengeId, + ':l': submission.challengeId + } + } + try { + yield dbhelper.updateRecord(record) + } catch (err) { + logger.error(`update submission record error: ${err.message}`) + failedContainer.push(submission) + } +} + +/* + * Update all submission's challenge id to v5 + * @returns {Promise} + */ +function * updateRecords () { + const tableName = config.SUBMISSION_TABLE_NAME + const promises = [] + const failedRecords = [] + // Process until all the records from DB is fetched + const challengeIds = yield helper.getLatestChallenges() + logger.debug(`Total number of challenges fetched from api - ${challengeIds.length}.`) + const batchIds = _.chunk(challengeIds, config.UPDATE_V5_CHALLENGE_BATCH_SIZE) + for (const cId of batchIds) { + const queryParams = _.fromPairs(_.map(cId, (c, i) => [`:challengeId${i}`, c.legacyId])) + const params = { + TableName: tableName, + FilterExpression: `#challengeId IN (${_.join(_.keys(queryParams), ',')})`, + ExpressionAttributeNames: { + '#challengeId': 'challengeId' + }, + ExpressionAttributeValues: queryParams + } + while (true) { + const records = yield dbhelper.scanRecords(params) + const totalRecords = records.Items.length + logger.debug(`Number of ${tableName}s fetched from DB - ${totalRecords}. More fetch iterations may follow (pagination in progress)`) + for (let i = 0; i < totalRecords; i++) { + const record = records.Items[i] + promises.push(updateRecord(record, failedRecords, _.find(cId, ['legacyId', record.challengeId]).id)) + } + // Continue fetching the remaining records from Database + if (typeof records.LastEvaluatedKey !== 'undefined') { + params.ExclusiveStartKey = records.LastEvaluatedKey + } else { + break // If there are no more records to process, exit the loop + } + } + } + logger.debug(`All records fetched. Proceeding to update them in batches of ${config.UPDATE_V5_CHALLENGE_BATCH_SIZE}`) + const paraRecords = _.chunk(promises, config.UPDATE_V5_CHALLENGE_BATCH_SIZE) + for (const rs of paraRecords) { + yield rs + } + logger.info(`Processed ${promises.length - failedRecords.length} records successfully`) + if (failedRecords.length > 0) { + logger.warn(`Processing of ${failedRecords.length} records failed`) + logger.info(`Failed records: ${_.join(_.map(failedRecords, f => JSON.stringify(_.pick(f, ['id', 'challengeId'])), ','))}`) + } +} + +co(function * () { + yield updateRecords() +}).catch((err) => { + logger.logFullError(err) +}) diff --git a/scripts/updateToV5ChallengeIdV2.js b/scripts/updateToV5ChallengeIdV2.js new file mode 100644 index 00000000..ba9e94c7 --- /dev/null +++ b/scripts/updateToV5ChallengeIdV2.js @@ -0,0 +1,87 @@ +/** + * Store v5 challenge id for current records + */ + +const _ = require('lodash') +const co = require('co') +const config = require('config') +const logger = require('../src/common/logger') +const dbhelper = require('../src/common/dbhelper') +const helper = require('../src/common/helper') + +/** + * Update Submission's challenge id to v5 + * @param {Object} submission The submission record + * @param {Array} failedContainer The failed records container + * @param {String} v5challengeId The v5 challenge id + * @returns {Promise} + */ +function * updateRecord (submission, failedContainer, v5challengeId) { + const record = { + TableName: 'Submission', + Key: { + id: submission.id + }, + UpdateExpression: `set challengeId = :c, legacyChallengeId = :l`, + ExpressionAttributeValues: { + ':c': v5challengeId, + ':l': submission.challengeId + } + } + try { + yield dbhelper.updateRecord(record) + } catch (err) { + logger.error(`update submission record error: ${err.message}`) + failedContainer.push(submission) + } +} + +/* + * Update all submission's challenge id to v5 + * @returns {Promise} + */ +function * updateRecords () { + const tableName = config.SUBMISSION_TABLE_NAME + const promises = [] + const failedRecords = [] + // Process until all the records from DB is fetched + const challengeIds = yield helper.getLatestChallenges() + logger.debug(`Number of challenges fetched from api - ${challengeIds.length}.`) + const params = { + TableName: tableName + } + while (true) { + const records = yield dbhelper.scanRecords(params) + const totalRecords = records.Items.length + logger.debug(`Number of ${tableName}s fetched from DB - ${totalRecords}. More fetch iterations may follow (pagination in progress)`) + for (let i = 0; i < totalRecords; i++) { + const record = records.Items[i] + const v5ChallengeId = _.get(_.find(challengeIds, ['legacyId', record.challengeId]), 'id') + if (v5ChallengeId) { + promises.push(updateRecord(record, failedRecords, v5ChallengeId)) + } + } + // Continue fetching the remaining records from Database + if (typeof records.LastEvaluatedKey !== 'undefined') { + params.ExclusiveStartKey = records.LastEvaluatedKey + } else { + break // If there are no more records to process, exit the loop + } + } + logger.debug(`All records fetched. Proceeding to update them in batches of ${config.UPDATE_V5_CHALLENGE_BATCH_SIZE}`) + const paraRecords = _.chunk(promises, config.UPDATE_V5_CHALLENGE_BATCH_SIZE) + for (const rs of paraRecords) { + yield rs + } + logger.info(`Processed ${promises.length - failedRecords.length} records successfully`) + if (failedRecords.length > 0) { + logger.warn(`Processing of ${failedRecords.length} records failed`) + logger.info(`Failed records: ${_.join(_.map(failedRecords, f => JSON.stringify(_.pick(f, ['id', 'challengeId'])), ','))}`) + } +} + +co(function * () { + yield updateRecords() +}).catch((err) => { + logger.logFullError(err) +}) diff --git a/src/common/helper.js b/src/common/helper.js index bf472f73..f3170bf9 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -53,7 +53,7 @@ function autoWrapExpress (obj) { return obj } _.each(obj, (value, key) => { - obj[key] = autoWrapExpress(value); //eslint-disable-line + obj[key] = autoWrapExpress(value); //eslint-disable-line }) return obj } @@ -225,6 +225,7 @@ function * fetchFromES (query, resource) { // Construct ES filter const filter = prepESFilter(query, resource) // Search with constructed filter + logger.debug(`The elasticsearch query is ${JSON.stringify(filter)}`) const docs = yield esClient.search(filter) // Extract data from hits const rows = _.map(docs.hits.hits, single => single._source) @@ -323,6 +324,30 @@ function * getLegacyChallengeId (challengeId) { return challengeId } +/** + * Get v5 challenge id (uuid) if legacy challenge id + * @param {Integer} challengeId Challenge ID + * @returns {String} v5 uuid Challenge ID of the given challengeId + */ +function * getV5ChallengeId (challengeId) { + if (!(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(challengeId))) { + logger.debug(`${challengeId} detected as legacy challenge id. Fetching legacy challenge id`) + const token = yield getM2Mtoken() + try { + const response = yield request.get(`${config.CHALLENGEAPI_V5_URL}?legacyId=${challengeId}`) + .set('Authorization', `Bearer ${token}`) + .set('Content-Type', 'application/json') + const v5Uuid = _.get(response, 'body[0].id') + logger.debug(`V5 challenge id is ${v5Uuid} for legacy challenge id ${challengeId}`) + return v5Uuid + } catch (err) { + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}?legacyId=${challengeId}`) + throw err + } + } + return challengeId +} + /* * Get submission phase ID of a challenge from Challenge API * @param challengeId Challenge ID @@ -331,24 +356,25 @@ function * getLegacyChallengeId (challengeId) { function * getSubmissionPhaseId (challengeId) { let phaseId = null let response + challengeId = yield getV5ChallengeId(challengeId) try { logger.info(`Calling to challenge API to find submission phase Id for ${challengeId}`) const token = yield getM2Mtoken() - response = yield request.get(`${config.CHALLENGEAPI_URL}/${challengeId}/phases`) + response = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') logger.info(`returned from finding submission phase Id for ${challengeId}`) } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}/${challengeId}/phases`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.debug('Setting submissionPhaseId to Null') response = null } if (response) { - const phases = _.get(response.body, 'result.content', []) - const checkPoint = _.filter(phases, { phaseType: 'Checkpoint Submission', phaseStatus: 'Open' }) - const submissionPh = _.filter(phases, { phaseType: 'Submission', phaseStatus: 'Open' }) - const finalFixPh = _.filter(phases, { phaseType: 'Final Fix', phaseStatus: 'Open' }) + const phases = _.get(response.body, 'phases', []) + const checkPoint = _.filter(phases, { name: 'Checkpoint Submission', isOpen: true }) + const submissionPh = _.filter(phases, { name: 'Submission', isOpen: true }) + const finalFixPh = _.filter(phases, { name: 'Final Fix', isOpen: true }) if (checkPoint.length !== 0) { phaseId = checkPoint[0].id } else if (submissionPh.length !== 0) { @@ -367,30 +393,65 @@ function * getSubmissionPhaseId (challengeId) { * @returns {Promise} */ function * checkCreateAccess (authUser, subEntity) { - let response + let challengeDetails + let resources + + const challengeId = yield getV5ChallengeId(subEntity.challengeId) // User can only create submission for themselves if (authUser.userId !== subEntity.memberId) { throw new errors.HttpStatusError(403, 'You are not allowed to submit on behalf of others') } + const token = yield getM2Mtoken() + try { - const token = yield getM2Mtoken() - logger.info(`Calling to challenge API for fetch phases and winners for ${subEntity.challengeId}`) - response = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${subEntity.challengeId}`) + logger.info(`Calling to challenge API for fetch phases and winners for ${challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') - logger.info(`returned for ${subEntity.challengeId} with ${JSON.stringify(response)}`) + logger.info(`returned for ${challengeId} with ${JSON.stringify(challengeDetails)}`) } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}?filter=id=${subEntity.challengeId}`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.error(ex) - return false + throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${challengeId}`) } - if (response) { - // Get phases and winner detail from response - const phases = response.body.result.content[0].allPhases - const winner = response.body.result.content[0].winners + try { + resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${challengeId}`) + .set('Authorization', `Bearer ${token}`) + .set('Content-Type', 'application/json') + } catch (ex) { + logger.error(`Error while accessing ${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${challengeId}`) + logger.error(ex) + throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${challengeId}`) + } + + // Get map of role id to role name + const resourceRolesMap = yield getRoleIdToRoleNameMap() + + // Check if role id to role name mapping is available. If not user's role cannot be determined. + if (resourceRolesMap == null || _.size(resourceRolesMap) === 0) { + throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${subEntity.challengeId}`) + } + + if (resources && challengeDetails) { + const currUserRoles = _.filter(resources.body, { memberHandle: authUser.handle }) + + // Populate the role names for the current user role ids + _.forEach(currUserRoles, currentUserRole => { + currentUserRole.role = resourceRolesMap[currentUserRole.roleId] + }) + + // Get phases and winner detail from challengeDetails + const phases = challengeDetails.body.phases + const winner = challengeDetails.body.winners + + // Check if the User is registered for the contest + const submitters = _.filter(currUserRoles, { role: 'Submitter' }) + if (submitters.length === 0) { + throw new errors.HttpStatusError(403, `Register for the contest before you can submit`) + } const submissionPhaseId = yield getSubmissionPhaseId(subEntity.challengeId) @@ -400,14 +461,16 @@ function * checkCreateAccess (authUser, subEntity) { const currPhase = _.filter(phases, { id: submissionPhaseId }) - if (currPhase[0].phaseType === 'Final Fix') { + if (currPhase[0].name === 'Final Fix') { if (!authUser.handle.equals(winner[0].handle)) { throw new errors.HttpStatusError(403, 'Only winner is allowed to submit during Final Fix phase') } } + } else { + // We don't have enough details to validate the access + logger.debug('No enough details to validate the Permissions') + throw new errors.HttpStatusError(503, `Not all information could be fetched about challenge with id ${subEntity.challengeId}`) } - - return true } /* @@ -425,32 +488,46 @@ function * checkGetAccess (authUser, submission) { } const token = yield getM2Mtoken() + const challengeId = yield getV5ChallengeId(submission.challengeId) try { - resources = yield request.get(`${config.CHALLENGEAPI_URL}/${submission.challengeId}/resources`) + resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}/${submission.challengeId}/resources`) + logger.error(`Error while accessing ${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${challengeId}`) logger.error(ex) - return false + throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${challengeId}`) } try { - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.error(ex) - return false + throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${challengeId}`) + } + + // Get map of role id to role name + const resourceRolesMap = yield getRoleIdToRoleNameMap() + + // Check if role id to role name mapping is available. If not user's role cannot be determined. + if (resourceRolesMap == null || _.size(resourceRolesMap) === 0) { + throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${challengeId}`) } if (resources && challengeDetails) { // Fetch all roles of the User pertaining to the current challenge - const currUserRoles = _.filter(resources.body.result.content, { properties: { Handle: authUser.handle } }) - const subTrack = challengeDetails.body.result.content[0].subTrack - const phases = challengeDetails.body.result.content[0].allPhases + const currUserRoles = _.filter(resources.body, { memberHandle: authUser.handle }) + + // Populate the role names for the current user role ids + _.forEach(currUserRoles, currentUserRole => { + currentUserRole.role = resourceRolesMap[currentUserRole.roleId] + }) + + const subTrack = challengeDetails.body.legacy.subTrack // Check if the User is a Copilot const copilot = _.filter(currUserRoles, { role: 'Copilot' }) @@ -473,18 +550,18 @@ function * checkGetAccess (authUser, submission) { // User is either a Reviewer or Screener if (screener.length !== 0 || reviewer.length !== 0) { - const screeningPhase = _.filter(phases, { phaseType: 'Screening', phaseStatus: 'Scheduled' }) - const reviewPhase = _.filter(phases, { phaseType: 'Review', phaseStatus: 'Scheduled' }) + const screeningPhaseStatus = getPhaseStatus('Screening', challengeDetails.body) + const reviewPhaseStatus = getPhaseStatus('Review', challengeDetails.body) // Neither Screening Nor Review is Opened / Closed - if (screeningPhase.length !== 0 && reviewPhase.length !== 0) { + if (screeningPhaseStatus === 'Scheduled' && reviewPhaseStatus === 'Scheduled') { throw new errors.HttpStatusError(403, 'You can access the submission only when Screening / Review is open') } } else { - const appealsResponse = _.filter(phases, { phaseType: 'Appeals Response', phaseStatus: 'Closed' }) + const appealsResponseStatus = getPhaseStatus('Appeals Response', challengeDetails.body) // Appeals Response is not closed yet - if (appealsResponse.length === 0) { + if (appealsResponseStatus !== 'Closed') { throw new errors.HttpStatusError(403, 'You cannot access other submissions before the end of Appeals Response phase') } else { const userSubmission = yield fetchFromES({ @@ -509,7 +586,7 @@ function * checkGetAccess (authUser, submission) { } else { // We don't have enough details to validate the access logger.debug('No enough details to validate the Permissions') - return true + throw new errors.HttpStatusError(503, `Not all information could be fetched about challenge with id ${submission.challengeId}`) } } @@ -522,30 +599,30 @@ function * checkGetAccess (authUser, submission) { function * checkReviewGetAccess (authUser, submission) { let challengeDetails const token = yield getM2Mtoken() + const challengeId = yield getV5ChallengeId(submission.challengeId) try { - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.error(ex) return false } if (challengeDetails) { - const subTrack = challengeDetails.body.result.content[0].subTrack - const phases = challengeDetails.body.result.content[0].allPhases + const subTrack = challengeDetails.body.legacy.subTrack // For Marathon Match, everyone can access review result if (subTrack === 'DEVELOP_MARATHON_MATCH') { logger.info('No access check for Marathon match') return true } else { - const appealsResponse = _.filter(phases, { phaseType: 'Appeals Response', phaseStatus: 'Closed' }) + const appealsResponseStatus = getPhaseStatus('Appeals Response', challengeDetails.body) // Appeals Response is not closed yet - if (appealsResponse.length === 0) { + if (appealsResponseStatus !== 'Closed') { throw new errors.HttpStatusError(403, 'You cannot access the review before the end of the Appeals Response phase') } @@ -621,6 +698,103 @@ function cleanseReviews (reviews, authUser) { return reviews } +/** + * Function to get role id to role name map + * @returns {Object|null} map + */ +function * getRoleIdToRoleNameMap () { + let resourceRoles + let resourceRolesMap = null + const token = yield getM2Mtoken() + try { + resourceRoles = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resource-roles`) + .set('Authorization', `Bearer ${token}`) + .set('Content-Type', 'application/json') + } catch (ex) { + logger.error(`Error while accessing ${config.RESOURCEAPI_V5_BASE_URL}/resource-roles`) + logger.error(ex) + resourceRoles = null + } + if (resourceRoles) { + resourceRolesMap = {} + _.forEach(resourceRoles.body, resourceRole => { + resourceRolesMap[resourceRole.id] = resourceRole.name + }) + } + return resourceRolesMap +} + +/** + * Function to get phase status of phases used in an active challenge + * @param {String} phaseName the phase name for retrieving status + * @param {Object} challengeDetails the challenge details + * @returns {('Scheduled' | 'Open' | 'Closed' | 'Invalid')} status of the phase + */ +function getPhaseStatus (phaseName, challengeDetails) { + const { phases } = challengeDetails + const queriedPhaseIndex = _.findIndex(phases, phase => { + return phase.name === phaseName + }) + // Requested phase name could not be found in phases hence 'Invalid' + if (queriedPhaseIndex === -1) { + return 'Invalid' + } + // If requested phase name is open return 'Open' + if (phases[queriedPhaseIndex].isOpen) { + return 'Open' + } else { + const { actualEndDate } = phases[queriedPhaseIndex] + if (!_.isEmpty(actualEndDate)) { + const present = new Date().getTime() + const actualDate = new Date(actualEndDate).getTime() + if (present > actualDate) { + return 'Closed' + } else { + return 'Scheduled' + } + } else { + return 'Scheduled' + } + } +} + +/** + * Change challengeId to v5ChallengeId and legacyChallengeId to challengeId + * @param {Object} submission + */ +function adjustSubmissionChallengeId (submission) { + if (submission.challengeId && submission.legacyChallengeId) { + submission.v5ChallengeId = submission.challengeId + submission.challengeId = submission.legacyChallengeId + } +} + +/** + * Get all latest challenges + * @param {Number} page page index + * @returns {Array} an array of challenge + */ +function * getLatestChallenges (page) { + page = page || 1 + const token = yield getM2Mtoken() + const url = `${config.CHALLENGEAPI_V5_URL}?createdDateStart=${config.FETCH_CREATED_DATE_START}&page=${page}&perPage=${config.FETCH_PAGE_SIZE}&isLightweight=true` + try { + const response = yield request.get(url) + .set('Authorization', `Bearer ${token}`) + .set('Content-Type', 'application/json') + const challenges = _.map(_.filter(_.get(response, 'body'), 'legacyId'), c => _.pick(c, 'id', 'legacyId')) + logger.debug(`Fetched ${challenges.length} challenges in this iteration. More may follow...`) + if (_.get(response, 'headers.x-total-pages') > page) { + const leftChallenges = yield getLatestChallenges(page + 1) + challenges.push(...leftChallenges) + } + return challenges + } catch (err) { + logger.error(`Error while accessing ${url}, message: ${err.message}`) + return [] + } +} + module.exports = { wrapExpress, autoWrapExpress, @@ -635,5 +809,9 @@ module.exports = { checkReviewGetAccess, downloadFile, postToBusApi, - cleanseReviews + cleanseReviews, + getRoleIdToRoleNameMap, + getV5ChallengeId, + adjustSubmissionChallengeId, + getLatestChallenges } diff --git a/src/common/logger.js b/src/common/logger.js index e9e40e76..429f7a43 100755 --- a/src/common/logger.js +++ b/src/common/logger.js @@ -10,7 +10,7 @@ const config = require('config') const getParams = require('get-parameter-names') const transports = [] -if (!config.DISABLE_LOGGING) { +if (!JSON.parse(config.DISABLE_LOGGING)) { transports.push(new (winston.transports.Console)({ level: config.LOG_LEVEL })) } const logger = new (winston.Logger)({ transports }) diff --git a/src/services/ReviewService.js b/src/services/ReviewService.js index a24ec142..ffd565d0 100644 --- a/src/services/ReviewService.js +++ b/src/services/ReviewService.js @@ -144,6 +144,14 @@ function * createReview (authUser, entity) { entity ) + if (_.intersection(authUser.roles, ['Administrator', 'administrator']).length === 0 && !authUser.scopes) { + if (entity.reviewedDate) { + throw new errors.HttpStatusError(403, 'You are not allowed to set the `reviewedDate` attribute on a review') + } + } + + item.reviewedDate = entity.reviewedDate || item.created + // Prepare record to be inserted const record = { TableName: table, @@ -202,7 +210,8 @@ createReview.schema = { .uuid() .required(), status: joi.reviewStatus(), - metadata: joi.object() + metadata: joi.object(), + reviewedDate: joi.string() }) .required() } @@ -237,7 +246,7 @@ function * _updateReview (authUser, reviewId, entity) { }, UpdateExpression: `set score = :s, scoreCardId = :sc, submissionId = :su, typeId = :t, reviewerId = :r, #st = :st, - updated = :ua, updatedBy = :ub`, + updated = :ua, updatedBy = :ub, reviewedDate = :rd`, ExpressionAttributeValues: { ':s': entity.score || exist.score, ':sc': entity.scoreCardId || exist.scoreCardId, @@ -246,7 +255,8 @@ function * _updateReview (authUser, reviewId, entity) { ':r': entity.reviewerId || exist.reviewerId, ':st': entity.status || exist.status || 'completed', ':ua': currDate, - ':ub': authUser.handle || authUser.sub + ':ub': authUser.handle || authUser.sub, + ':rd': entity.reviewedDate || exist.reviewedDate || exist.created }, ExpressionAttributeNames: { '#st': 'status' @@ -278,7 +288,8 @@ function * _updateReview (authUser, reviewId, entity) { resource: helper.camelize(table), id: reviewId, updated: currDate, - updatedBy: authUser.handle || authUser.sub + updatedBy: authUser.handle || authUser.sub, + reviewedDate: entity.reviewedDate || exist.reviewedDate || exist.created }, entity ) @@ -291,7 +302,8 @@ function * _updateReview (authUser, reviewId, entity) { // Hence returning the response which will be in compliance with Swagger return _.extend(exist, entity, { updated: currDate, - updatedBy: authUser.handle || authUser.sub + updatedBy: authUser.handle || authUser.sub, + reviewedDate: entity.reviewedDate || exist.reviewedDate || exist.created }) } @@ -330,7 +342,8 @@ updateReview.schema = { .uuid() .required(), status: joi.reviewStatus(), - metadata: joi.object() + metadata: joi.object(), + reviewedDate: joi.string() }) .required() } @@ -359,7 +372,8 @@ patchReview.schema = { scoreCardId: joi.id(), submissionId: joi.string().uuid(), status: joi.reviewStatus(), - metadata: joi.object() + metadata: joi.object(), + reviewedDate: joi.string() }) } diff --git a/src/services/ReviewSummationService.js b/src/services/ReviewSummationService.js index e455243d..69af4aa2 100644 --- a/src/services/ReviewSummationService.js +++ b/src/services/ReviewSummationService.js @@ -102,6 +102,14 @@ function * createReviewSummation (authUser, entity) { item.isFinal = entity.isFinal } + if (_.intersection(authUser.roles, ['Administrator', 'administrator']).length === 0 && !authUser.scopes) { + if (entity.reviewedDate) { + throw new errors.HttpStatusError(403, 'You are not allowed to set the `reviewedDate` attribute on a review summation') + } + } + + item.reviewedDate = entity.reviewedDate || item.created + const record = { TableName: table, Item: item @@ -135,7 +143,8 @@ createReviewSummation.schema = { aggregateScore: joi.score().required(), isPassing: joi.boolean().required(), isFinal: joi.boolean(), - metadata: joi.object() + metadata: joi.object(), + reviewedDate: joi.string() }).required() } @@ -173,14 +182,15 @@ function * _updateReviewSummation (authUser, reviewSummationId, entity) { id: reviewSummationId }, UpdateExpression: `set aggregateScore = :s, scoreCardId = :sc, submissionId = :su, - isPassing = :ip, updated = :ua, updatedBy = :ub`, + isPassing = :ip, updated = :ua, updatedBy = :ub, reviewedDate = :rd`, ExpressionAttributeValues: { ':s': entity.aggregateScore || exist.aggregateScore, ':sc': entity.scoreCardId || exist.scoreCardId, ':su': entity.submissionId || exist.submissionId, ':ip': isPassing, ':ua': currDate, - ':ub': authUser.handle || authUser.sub + ':ub': authUser.handle || authUser.sub, + ':rd': entity.reviewedDate || exist.reviewedDate || exist.created } } @@ -217,7 +227,8 @@ function * _updateReviewSummation (authUser, reviewSummationId, entity) { resource: helper.camelize(table), id: reviewSummationId, updated: currDate, - updatedBy: authUser.handle || authUser.sub + updatedBy: authUser.handle || authUser.sub, + reviewedDate: entity.reviewedDate || exist.reviewedDate || exist.created }, entity) } @@ -226,7 +237,15 @@ function * _updateReviewSummation (authUser, reviewSummationId, entity) { // Updating records in DynamoDB doesn't return any response // Hence returning the response which will be in compliance with Swagger - return _.extend(exist, entity, { updated: currDate, updatedBy: authUser.handle || authUser.sub }) + return _.extend( + exist, + entity, + { + updated: currDate, + updatedBy: authUser.handle || authUser.sub, + reviewedDate: entity.reviewedDate || exist.reviewedDate || exist.created + } + ) } /** @@ -249,7 +268,8 @@ updateReviewSummation.schema = { aggregateScore: joi.score().required(), isPassing: joi.boolean().required(), isFinal: joi.boolean(), - metadata: joi.object() + metadata: joi.object(), + reviewedDate: joi.string() }).required() } @@ -273,7 +293,8 @@ patchReviewSummation.schema = { aggregateScore: joi.score(), isPassing: joi.boolean(), isFinal: joi.boolean(), - metadata: joi.object() + metadata: joi.object(), + reviewedDate: joi.string() }) } diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index 0403872e..35dc0122 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -154,6 +154,7 @@ function * getSubmission (authUser, submissionId) { // Return the retrieved submission logger.info(`getSubmission: returning data for submissionId: ${submissionId}`) + helper.adjustSubmissionChallengeId(submissionRecord) return submissionRecord } @@ -182,9 +183,9 @@ function * downloadSubmission (authUser, submissionId) { */ function * listSubmissions (authUser, query) { if (query.challengeId) { - // Submission api only works with legacy challenge id - // If it is a v5 challenge id, get the associated legacy challenge id - query.challengeId = yield helper.getLegacyChallengeId(query.challengeId) + // Submission api now only works with v5 challenge id + // If it is a legacy challenge id, get the associated v5 challenge id + query.challengeId = yield helper.getV5ChallengeId(query.challengeId) } const data = yield helper.fetchFromES(query, helper.camelize(table)) @@ -194,6 +195,7 @@ function * listSubmissions (authUser, query) { if (submission.review) { submission.review = helper.cleanseReviews(submission.review, authUser) } + helper.adjustSubmissionChallengeId(submission) return submission }) return data @@ -270,8 +272,8 @@ function * createSubmission (authUser, files, entity) { // Submission api only works with legacy challenge id // If it is a v5 challenge id, get the associated legacy challenge id - const challengeId = yield helper.getLegacyChallengeId(entity.challengeId) - + const challengeId = yield helper.getV5ChallengeId(entity.challengeId) + const legacyChallengeId = yield helper.getLegacyChallengeId(entity.challengeId) const currDate = (new Date()).toISOString() const item = { @@ -279,7 +281,8 @@ function * createSubmission (authUser, files, entity) { type: entity.type, url: url, memberId: entity.memberId, - challengeId: challengeId, + challengeId, + legacyChallengeId, created: currDate, updated: currDate, createdBy: authUser.handle || authUser.sub, @@ -297,7 +300,7 @@ function * createSubmission (authUser, files, entity) { if (entity.submissionPhaseId) { item.submissionPhaseId = entity.submissionPhaseId } else { - item.submissionPhaseId = yield helper.getSubmissionPhaseId(challengeId) + item.submissionPhaseId = yield helper.getSubmissionPhaseId(entity.challengeId) } if (entity.fileType) { @@ -310,10 +313,16 @@ function * createSubmission (authUser, files, entity) { if (_.intersection(authUser.roles, ['Administrator', 'administrator']).length === 0 && !authUser.scopes) { logger.info(`Calling checkCreateAccess for ${JSON.stringify(authUser)}`) yield helper.checkCreateAccess(authUser, item) + + if (entity.submittedDate) { + throw new errors.HttpStatusError(403, 'You are not allowed to set the `submittedDate` attribute on a submission') + } } else { logger.info(`No need to call checkCreateAccess for ${JSON.stringify(authUser)}`) } + item.submittedDate = entity.submittedDate || item.created + // Prepare record to be inserted const record = { TableName: table, @@ -323,6 +332,9 @@ function * createSubmission (authUser, files, entity) { logger.info('Prepared submission item to insert into Dynamodb. Inserting...') yield dbhelper.insertRecord(record) + // After save to db, adjust challengeId to busApi and response + helper.adjustSubmissionChallengeId(item) + // Push Submission created event to Bus API // Request body for Posting to Bus API const reqBody = { @@ -363,7 +375,8 @@ createSubmission.schema = { challengeId: joi.alternatives().try(joi.id(), joi.string().uuid()).required(), legacySubmissionId: joi.alternatives().try(joi.id(), joi.string().uuid()), legacyUploadId: joi.alternatives().try(joi.id(), joi.string().uuid()), - submissionPhaseId: joi.id() + submissionPhaseId: joi.id(), + submittedDate: joi.string() }).required() } @@ -384,13 +397,13 @@ function * _updateSubmission (authUser, submissionId, entity) { throw new errors.HttpStatusError(404, `Submission with ID = ${submissionId} is not found`) } + const currDate = (new Date()).toISOString() + let challengeId = exist.challengeId + let legacyChallengeId = exist.legacyChallengeId if (entity.challengeId) { - // Submission api only works with legacy challenge id - // If it is a v5 challenge id, get the associated legacy challenge id - entity.challengeId = yield helper.getLegacyChallengeId(entity.challengeId) + challengeId = yield helper.getV5ChallengeId(entity.challengeId) + legacyChallengeId = yield helper.getLegacyChallengeId(entity.challengeId) } - - const currDate = (new Date()).toISOString() // Record used for updating in Database const record = { TableName: table, @@ -398,14 +411,16 @@ function * _updateSubmission (authUser, submissionId, entity) { id: submissionId }, UpdateExpression: `set #type = :t, #url = :u, memberId = :m, challengeId = :c, - updated = :ua, updatedBy = :ub`, + legacyChallengeId = :lc, updated = :ua, updatedBy = :ub, submittedDate = :sb`, ExpressionAttributeValues: { ':t': entity.type || exist.type, ':u': entity.url || exist.url, ':m': entity.memberId || exist.memberId, - ':c': entity.challengeId || exist.challengeId, + ':c': challengeId, + ':lc': legacyChallengeId, ':ua': currDate, - ':ub': authUser.handle || authUser.sub + ':ub': authUser.handle || authUser.sub, + ':sb': entity.submittedDate || exist.submittedDate || exist.created }, ExpressionAttributeNames: { '#type': 'type', @@ -436,6 +451,7 @@ function * _updateSubmission (authUser, submissionId, entity) { yield dbhelper.updateRecord(record) const updatedSub = yield _getSubmission(submissionId) + helper.adjustSubmissionChallengeId(updatedSub) // Push Submission updated event to Bus API // Request body for Posting to Bus API const reqBody = { @@ -447,9 +463,11 @@ function * _updateSubmission (authUser, submissionId, entity) { resource: helper.camelize(table), id: submissionId, challengeId: updatedSub.challengeId, + v5ChallengeId: updatedSub.v5ChallengeId, memberId: updatedSub.memberId, submissionPhaseId: updatedSub.submissionPhaseId, - type: updatedSub.type + type: updatedSub.type, + submittedDate: updatedSub.submittedDate }, entity) } @@ -458,7 +476,17 @@ function * _updateSubmission (authUser, submissionId, entity) { // Updating records in DynamoDB doesn't return any response // Hence returning the response which will be in compliance with Swagger - return _.extend(exist, entity, { updated: currDate, updatedBy: authUser.handle || authUser.sub }) + return _.extend( + exist, + entity, + { + updated: currDate, + updatedBy: authUser.handle || authUser.sub, + submittedDate: updatedSub.submittedDate, + challengeId: updatedSub.challengeId, + v5ChallengeId: updatedSub.v5ChallengeId + } + ) } /** @@ -482,7 +510,8 @@ updateSubmission.schema = { challengeId: joi.alternatives().try(joi.id(), joi.string().uuid()).required(), legacySubmissionId: joi.alternatives().try(joi.id(), joi.string().uuid()), legacyUploadId: joi.alternatives().try(joi.id(), joi.string().uuid()), - submissionPhaseId: joi.id() + submissionPhaseId: joi.id(), + submittedDate: joi.string() }).required() } @@ -507,7 +536,8 @@ patchSubmission.schema = { challengeId: joi.alternatives().try(joi.id(), joi.string().uuid()), legacySubmissionId: joi.alternatives().try(joi.id(), joi.string().uuid()), legacyUploadId: joi.alternatives().try(joi.id(), joi.string().uuid()), - submissionPhaseId: joi.id() + submissionPhaseId: joi.id(), + submittedDate: joi.string() }) } diff --git a/test/common/testData.js b/test/common/testData.js index 2a2bb39f..fdeddb2a 100644 --- a/test/common/testData.js +++ b/test/common/testData.js @@ -121,19 +121,63 @@ const testReviewTypesES = { const nonExSubmissionId = 'b3564180-65aa-42ec-a945-5fd21dec0502' +const testChallengeResources = [ + { + 'id': '9a06daeb-1b8e-4d91-9bd4-c5fda7c93db2', + 'challengeId': '9131c5da-6ed9-4186-9a1b-4de31df5ba17', + 'memberId': '88774396', + 'memberHandle': 'Sharathkumar92', + 'roleId': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bd', + 'created': '2021-02-02T22:51:59.000Z', + 'createdBy': 'jmgasper' + }, + { + 'id': '9a06daeb-1b8e-4d91-9bd4-c5fda7c93db2', + 'challengeId': '9131c5da-6ed9-4186-9a1b-4de31df5ba17', + 'memberId': '88774396', + 'memberHandle': 'Sharathkumar92', + 'roleId': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bb', + 'created': '2021-02-02T22:51:59.000Z', + 'createdBy': 'jmgasper' + } +] + +const testResourceRoles = [ + { + 'id': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bd', + 'name': 'Copilot', + 'legacyId': 14, + 'fullReadAccess': true, + 'fullWriteAccess': true, + 'isActive': true, + 'selfObtainable': false + }, + { + 'id': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bb', + 'name': 'Submitter', + 'legacyId': 14, + 'fullReadAccess': true, + 'fullWriteAccess': true, + 'isActive': true, + 'selfObtainable': false + } +] + const testSubmission = { Item: { challengeId: 'c3564180-65aa-42ec-a945-5fd21dec0502', id: 'a12a4180-65aa-42ec-a945-5fd21dec0501', type: 'ContestSubmission', url: 'https://software.topcoder.com/review/actions/DownloadContestSubmission?uid=123456', - memberId: 'b24d4180-65aa-42ec-a945-5fd21dec0501', + memberId: 40493050, legacySubmissionId: 'b24d4180-65aa-42ec-a945-5fd21dec0501', submissionPhaseId: 764567, created: '2018-05-20T07:00:30.123Z', createdBy: 'topcoder user', updated: '2018-06-01T07:36:28.178Z', - updatedBy: 'topcoder user' + updatedBy: 'topcoder user', + review: [], + reviewSummation: [] } } @@ -385,6 +429,7 @@ const testReview = { id: 'd24d4180-65aa-42ec-a945-5fd21dec0502', score: 92, reviewerId: 'c23a4180-65aa-42ec-a945-5fd21dec0503', + reviewedDate: '2021-02-02T11:39:38.685Z', submissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0501', scoreCardId: 123456789, status: 'queued', @@ -401,6 +446,7 @@ const testReviewPatch = { id: 'd24d4180-65aa-42ec-a945-5fd21dec0502', score: 90, reviewerId: 'c23a4180-65aa-42ec-a945-5fd21dec0503', + reviewedDate: '2021-02-02T11:39:38.685Z', submissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0501', scoreCardId: 123456789, status: 'queued', @@ -535,6 +581,7 @@ const testReviewSummation = { aggregateScore: 99, isPassing: true, submissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0501', + reviewedDate: '2021-02-02T11:39:38.685Z', scoreCardId: 123456789, created: '2018-05-20T07:00:30.123Z', updated: '2018-06-01T07:36:28.178Z', @@ -549,6 +596,7 @@ const testReviewSummationPatch = { aggregateScore: 78.5, isPassing: false, submissionId: 'a12a4180-65aa-42ec-a945-5fd21dec0501', + reviewedDate: '2021-02-02T11:39:38.685Z', scoreCardId: 123456789, created: '2018-05-20T07:00:30.123Z', updated: '2018-06-01T07:36:28.178Z', @@ -664,98 +712,180 @@ const testReviewSummationsES = { } const testChallengeAPIResponse = { - id: '24a97f2f:1655fef5034:-7568', - result: { - success: true, - status: 200, - metadata: { - fields: null, - totalCount: 5 + 'id': '77eb9522-ea41-4334-974d-7604097d23e7', + 'created': '2020-11-02T21:34:19Z', + 'createdBy': 'tcwebservice', + 'updated': '2020-12-28T06:44:27Z', + 'updatedBy': 'AutoPilot', + 'status': 'Active', + 'projectId': 16661, + 'name': 'TCO Leaderboard Test 3', + 'typeId': '927abff4-7af9-4145-8ba1-577c16e64e2e', + 'trackId': '9b6fc876-f4d9-4ccb-9dfd-419247628825', + 'startDate': '2020-12-21T18:24:09Z', + 'legacy': { + 'reviewType': 'COMMUNITY', + 'isTask': false, + 'subTrack': 'CODE', + 'directProjectId': 23741, + 'track': 'DEVELOP', + 'reviewScorecardId': 30001610, + 'forumId': 0 + }, + 'descriptionFormat': 'HTML', + 'timelineTemplateId': '7ebf1c69-f62f-4d3a-bdfb-fe9ddb56861c', + 'terms': [ + { + 'roleId': '732339e7-8e30-49d7-9198-cccf9451e221', + 'id': 'b11da5cd-713f-478d-90f4-f679ef53ee95' + }, + { + 'roleId': '3eedd4a4-3c68-4f68-8de4-a1ca5c2055e5', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' + }, + { + 'roleId': '318b9c07-079a-42d9-a81f-b96be1dc1099', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' }, - content: [ - { - challengeId: 30049360, - id: 733195, - phaseType: 'Registration', - phaseStatus: 'Open', - scheduledStartTime: '1438002000000', - scheduledEndTime: '2019-12-02T09:00:00Z', - actualStartTime: '1438002000000', - actualEndTime: null, - fixedStartTime: '1438002000000', - duration: 137293200000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - }, - { - challengeId: 30049360, - id: 733196, - phaseType: 'Submission', - phaseStatus: 'Open', - scheduledStartTime: '1438002300000', - scheduledEndTime: '2019-12-02T09:00:00Z', - actualStartTime: null, - actualEndTime: null, - fixedStartTime: null, - duration: 137292900000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - }, - { - challengeId: 30049360, - id: 733197, - phaseType: 'Review', - phaseStatus: 'Scheduled', - scheduledStartTime: '1575295200000', - scheduledEndTime: '2019-12-04T09:00:00Z', - actualStartTime: null, - actualEndTime: null, - fixedStartTime: null, - duration: 172800000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - }, - { - challengeId: 30049360, - id: 733198, - phaseType: 'Appeals', - phaseStatus: 'Scheduled', - scheduledStartTime: '1575468000000', - scheduledEndTime: '2019-12-05T09:00:00Z', - actualStartTime: null, - actualEndTime: null, - fixedStartTime: null, - duration: 86400000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - }, - { - challengeId: 30049360, - id: 733199, - phaseType: 'Appeals Response', - phaseStatus: 'Scheduled', - scheduledStartTime: '1575554400000', - scheduledEndTime: '2019-12-05T09:00:00Z', - actualStartTime: null, - actualEndTime: null, - fixedStartTime: null, - duration: 43200000, - updatedAt: '2018-07-30T08:38Z', - createdAt: '2015-07-27T09:19Z', - createdBy: '11823846', - updatedBy: '8547899' - } - ] + { + 'roleId': 'ff556573-5da6-4392-b38c-08c1d7599c4a', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' + }, + { + 'roleId': 'e0544b94-6420-4afc-8f63-238eddc751b9', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' + }, + { + 'roleId': '0e9c6879-39e4-4eb6-b8df-92407890faf1', + 'id': '75d2f6bb-aadc-475e-9728-32c1dbd13655' + }, + { + 'roleId': 'cfe12b3f-2a24-4639-9d8b-ec86726f76bd', + 'id': 'e0993b1a-abf7-45e6-8ed9-8cd0546be90b' + }, + { + 'roleId': 'd663fc84-5c37-43d1-a537-793feffb7667', + 'id': '82a35602-57c2-4b48-a9b9-b4e133b22035' + } + ], + 'phases': [ + { + 'duration': 561600, + 'scheduledEndDate': '2020-12-28T06:44:27Z', + 'actualEndDate': '2020-12-28T06:44:27Z', + 'isOpen': false, + 'name': 'Registration', + 'phaseId': 'a93544bc-c165-4af4-b55e-18f3593b457a', + 'actualStartDate': '2020-12-21T18:24:09Z', + 'id': 'f6166029-cdef-4b72-b7a4-f2d3074bafac', + 'scheduledStartDate': '2020-12-21T18:24:09Z' + }, + { + 'duration': 561300, + 'scheduledEndDate': '2020-12-28T06:44:28Z', + 'actualEndDate': '2020-12-28T06:44:28Z', + 'isOpen': true, + 'name': 'Submission', + 'phaseId': '6950164f-3c5e-4bdc-abc8-22aaf5a1bd49', + 'actualStartDate': '2020-12-21T18:44:58Z', + 'id': '90ddb27a-cc49-454c-8367-354011eeba73', + 'scheduledStartDate': '2020-12-21T18:44:58Z' + }, + { + 'duration': 172800, + 'scheduledEndDate': '2020-12-30T06:44:00Z', + 'actualEndDate': '2020-12-28T06:51:27Z', + 'isOpen': false, + 'name': 'Review', + 'phaseId': 'aa5a3f78-79e0-4bf7-93ff-b11e8f5b398b', + 'actualStartDate': '2020-12-28T06:51:27Z', + 'id': '35a75a3f-c9ba-46ad-8003-f605c9bb4791', + 'scheduledStartDate': '2020-12-28T06:44:28Z' + }, + { + 'duration': 86400, + 'scheduledEndDate': '2020-12-31T06:44:00Z', + 'actualEndDate': '2020-12-28T06:51:27Z', + 'isOpen': false, + 'name': 'Appeals', + 'phaseId': '1c24cfb3-5b0a-4dbd-b6bd-4b0dff5349c6', + 'actualStartDate': '2020-12-28T06:51:27Z', + 'id': '3d16078a-2362-41fe-af82-01112b8f27c8', + 'scheduledStartDate': '2020-12-30T06:44:00Z' + }, + { + 'duration': 43200, + 'scheduledEndDate': '2020-12-31T18:44:00Z', + 'actualEndDate': '2020-12-28T06:51:27Z', + 'isOpen': false, + 'name': 'Appeals Response', + 'phaseId': '797a6af7-cd3f-4436-9fca-9679f773bee9', + 'actualStartDate': '2020-12-28T06:51:27Z', + 'id': '2359d4fd-aa1a-4403-98c5-a1f841b8062e', + 'scheduledStartDate': '2020-12-31T06:44:00Z' + }, + { + 'duration': 86400, + 'scheduledEndDate': '2020-12-29T06:48:00Z', + 'actualEndDate': '2020-12-28T06:51:27Z', + 'isOpen': true, + 'name': 'Post-Mortem', + 'phaseId': 'f308bdb4-d3da-43d8-942b-134dfbaf5c45', + 'actualStartDate': '2020-12-28T06:48:44Z', + 'id': '3a579100-e334-4b8f-ac89-7c8c696d42f0', + 'scheduledStartDate': '2020-12-28T06:48:44Z' + } + ], + 'discussions': [ + { + 'provider': 'vanilla', + 'name': 'TCO Leaderboard Test 3 Discussion', + 'id': 'cfbb21e8-a67a-4a23-997c-04022894d958', + 'type': 'challenge', + 'url': 'https://vanilla.topcoder-dev.com/categories/77eb9522-ea41-4334-974d-7604097d23e7' + } + ], + 'description': 'test', + 'groups': [], + 'endDate': '2020-12-29T06:48:00Z', + 'numOfSubmissions': 0, + 'numOfRegistrants': 0, + 'currentPhaseNames': [ + 'Post-Mortem' + ], + 'registrationStartDate': '2020-12-21T18:24:09Z', + 'registrationEndDate': '2020-12-28T06:44:27Z', + 'submissionStartDate': '2020-12-21T18:44:58Z', + 'submissionEndDate': '2020-12-28T06:44:28Z', + 'track': 'Development', + 'type': 'Challenge', + 'attachments': [], + 'prizeSets': [ + { + 'prizes': [ + { + 'type': 'USD', + 'value': 1 + } + ], + 'description': 'Challenge Prizes', + 'type': 'placement' + } + ], + 'tags': [ + 'Automated Testing' + ], + 'legacyId': 30057477, + 'metadata': [], + 'events': [], + 'task': { + 'isAssigned': false, + 'isTask': false, + 'memberId': null }, - version: 'v4' + 'overview': { + 'totalPrizes': 1 + } } module.exports = { @@ -780,5 +910,7 @@ module.exports = { testReviewSummationPatch, testReviewSummationES, testReviewSummationsES, - testChallengeAPIResponse + testChallengeAPIResponse, + testResourceRoles, + testChallengeResources } diff --git a/test/unit/ReviewService.test.js b/test/unit/ReviewService.test.js index 62181110..b7e3d71a 100644 --- a/test/unit/ReviewService.test.js +++ b/test/unit/ReviewService.test.js @@ -75,8 +75,7 @@ describe('Review Service tests', () => { .get(`${config.API_VERSION}/reviews/${testReview.Item.id}`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) .end((err, res) => { - res.should.have.status(200) - res.body.should.have.all.keys(Object.keys(testReview.Item)) + res.body.should.have.all.keys(Object.keys(_.omit(testReview.Item, ['reviewedDate']))) res.body.id.should.be.eql(testReview.Item.id) res.body.score.should.be.eql(testReview.Item.score) res.body.reviewerId.should.be.eql(testReview.Item.reviewerId) @@ -99,7 +98,7 @@ describe('Review Service tests', () => { .send({}) .end((err, res) => { res.should.have.status(400) - res.body.message.should.be.eql('"score" is required') + res.body.message.should.be.eql('"typeId" is required') done() }) }) diff --git a/test/unit/ReviewSummationService.test.js b/test/unit/ReviewSummationService.test.js index af8f0213..ee9a7311 100644 --- a/test/unit/ReviewSummationService.test.js +++ b/test/unit/ReviewSummationService.test.js @@ -76,7 +76,7 @@ describe('Review Summation Service tests', () => { .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) .end((err, res) => { res.should.have.status(200) - res.body.should.have.all.keys(Object.keys(testReviewSummation.Item)) + res.body.should.have.all.keys(Object.keys(_.omit(testReviewSummation.Item, ['reviewedDate']))) res.body.id.should.be.eql(testReviewSummation.Item.id) res.body.aggregateScore.should.be.eql(testReviewSummation.Item.aggregateScore) res.body.submissionId.should.be.eql(testReviewSummation.Item.submissionId) diff --git a/test/unit/ReviewTypeService.test.js b/test/unit/ReviewTypeService.test.js index 8777aa65..922f519c 100644 --- a/test/unit/ReviewTypeService.test.js +++ b/test/unit/ReviewTypeService.test.js @@ -429,13 +429,12 @@ describe('ReviewType Service tests', () => { }) }) - it('Getting review types with user token should throw 403', (done) => { + it('Getting review types with user token should return 200', (done) => { chai.request(app) .get(`${config.API_VERSION}/reviewTypes`) .set('Authorization', `Bearer ${config.USER_TOKEN}`) .end((err, res) => { - res.should.have.status(403) - res.body.message.should.be.eql('You are not allowed to perform this action!') + res.should.have.status(200) done() }) }) diff --git a/test/unit/SubmissionService.test.js b/test/unit/SubmissionService.test.js index 9e92daaf..652e0cba 100644 --- a/test/unit/SubmissionService.test.js +++ b/test/unit/SubmissionService.test.js @@ -14,7 +14,7 @@ const chaiHttp = require('chai-http') const should = chai.should() // eslint-disable-line const app = require('../../app') const { - nonExSubmissionId, testSubmission, testSubmissionWoLegacy, + nonExSubmissionId, testSubmission, testSubmissionPatch } = require('../common/testData') @@ -65,7 +65,7 @@ describe('Submission Service tests', () => { .set('Authorization', `Bearer ${config.USER_TOKEN}`) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(testSubmission.Item)) + res.body.should.have.keys(Object.keys(_.omit(testSubmission.Item, ['submittedDate']))) res.body.id.should.be.eql(testSubmission.Item.id) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -80,7 +80,7 @@ describe('Submission Service tests', () => { .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) .end((err, res) => { res.should.have.status(200) - res.body.should.have.all.keys(Object.keys(testSubmission.Item)) + res.body.should.have.all.keys(Object.keys(_.omit(testSubmission.Item, ['submittedDate']))) res.body.id.should.be.eql(testSubmission.Item.id) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -135,7 +135,7 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.USER_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'url', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'url', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(400) res.body.message.should.be.eql('Either file to be uploaded or URL should be present') @@ -163,9 +163,9 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.USER_TOKEN}`) - .field('challengeId', testSubmissionWoLegacy.Item.challengeId) - .field('type', testSubmissionWoLegacy.Item.type) - .field('memberId', testSubmissionWoLegacy.Item.memberId) + .field('challengeId', testSubmission.Item.challengeId) + .field('type', testSubmission.Item.type) + .field('memberId', testSubmission.Item.memberId) .field('fileType', 'pdf') .attach('submission', './test/common/fileToUpload.zip', 'fileToUpload.zip') .end((err, res) => { @@ -179,21 +179,21 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.USER_TOKEN}`) - .field('challengeId', testSubmissionWoLegacy.Item.challengeId) - .field('type', testSubmissionWoLegacy.Item.type) - .field('memberId', testSubmissionWoLegacy.Item.memberId) + .field('challengeId', testSubmission.Item.challengeId) + .field('type', testSubmission.Item.type) + .field('memberId', testSubmission.Item.memberId) .field('fileType', 'zip') .attach('submission', './test/common/fileToUpload.zip', 'fileToUpload.zip') .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip', submissionPhaseId: 733196 }, testSubmissionWoLegacy.Item))) + res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip', submissionPhaseId: 733196, submittedDate: '2018-05-20T07:00:30.123Z' }, _.omit(testSubmission.Item, ['legacySubmissionId', 'review', 'reviewSummation'])))) res.body.id.should.not.be.eql(null) - res.body.challengeId.should.be.eql(testSubmissionWoLegacy.Item.challengeId) - res.body.type.should.be.eql(testSubmissionWoLegacy.Item.type) + res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) + res.body.type.should.be.eql(testSubmission.Item.type) res.body.url.should.not.be.eql(null) - res.body.memberId.should.be.eql(testSubmissionWoLegacy.Item.memberId) + res.body.memberId.should.be.eql(testSubmission.Item.memberId) res.body.fileType.should.be.eql('zip') - res.body.submissionPhaseId.should.be.eql(733196) + res.body.submissionPhaseId.should.be.eql('90ddb27a-cc49-454c-8367-354011eeba73') done() }) }) @@ -216,10 +216,10 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip' }, testSubmission.Item))) + res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip', submissionPhaseId: 733196, submittedDate: '2018-05-20T07:00:30.123Z' }, _.omit(testSubmission.Item, ['review', 'reviewSummation'])))) res.body.id.should.not.be.eql(null) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -233,10 +233,10 @@ describe('Submission Service tests', () => { chai.request(app) .post(`${config.API_VERSION}/submissions`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'submissionPhaseId'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'submissionPhaseId', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip' }, testSubmission.Item))) + res.body.should.have.keys(Object.keys(_.extend({ fileType: 'zip', submissionPhaseId: 733196, submittedDate: '2018-05-20T07:00:30.123Z' }, _.omit(testSubmission.Item, ['review', 'reviewSummation'])))) res.body.id.should.not.be.eql(null) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -316,7 +316,7 @@ describe('Submission Service tests', () => { chai.request(app) .put(`${config.API_VERSION}/submissions/${nonExSubmissionId}`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(404) res.body.message.should.be.eql(`Submission with ID = ${nonExSubmissionId} is not found`) @@ -328,10 +328,10 @@ describe('Submission Service tests', () => { chai.request(app) .put(`${config.API_VERSION}/submissions/${testSubmission.Item.id}`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(testSubmission.Item)) + res.body.should.have.keys(Object.keys(_.extend({ submissionPhaseId: 733196 }, _.omit(testSubmission.Item, ['review', 'reviewSummation', 'submittedDate'])))) res.body.id.should.not.be.eql(null) res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) res.body.type.should.be.eql(testSubmission.Item.type) @@ -342,16 +342,16 @@ describe('Submission Service tests', () => { it('Updating submission without legacy fields with Admin token should get succeeded', (done) => { chai.request(app) - .put(`${config.API_VERSION}/submissions/${testSubmissionWoLegacy.Item.id}`) + .put(`${config.API_VERSION}/submissions/${testSubmission.Item.id}`) .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) - .send(_.omit(testSubmissionWoLegacy.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy'])) + .send(_.omit(testSubmission.Item, ['id', 'created', 'updated', 'createdBy', 'updatedBy', 'review', 'reviewSummation'])) .end((err, res) => { res.should.have.status(200) - res.body.should.have.keys(Object.keys(testSubmissionWoLegacy.Item)) + res.body.should.have.keys(Object.keys(_.extend({ submissionPhaseId: 733196 }, _.omit(testSubmission.Item, ['review', 'reviewSummation', 'submittedDate'])))) res.body.id.should.not.be.eql(null) - res.body.challengeId.should.be.eql(testSubmissionWoLegacy.Item.challengeId) - res.body.type.should.be.eql(testSubmissionWoLegacy.Item.type) - res.body.url.should.be.eql(testSubmissionWoLegacy.Item.url) + res.body.challengeId.should.be.eql(testSubmission.Item.challengeId) + res.body.type.should.be.eql(testSubmission.Item.type) + res.body.url.should.be.eql(testSubmission.Item.url) done() }) }).timeout(10000) diff --git a/test/unit/prepare.js b/test/unit/prepare.js index 824420db..9a476250 100644 --- a/test/unit/prepare.js +++ b/test/unit/prepare.js @@ -34,6 +34,16 @@ prepare(function (done) { } }) + AWS.mock('DynamoDB.DocumentClient', 'query', (params, callback) => { + if (params.ExpressionAttributeValues[':p_submissionId'] === testData.nonExSubmissionId) { + callback(null, { + Count: 0 + }) + } else if (params.ExpressionAttributeValues[':p_submissionId'] === testData.testSubmission.Item.id) { + callback(null, []) + } + }) + AWS.mock('DynamoDB.DocumentClient', 'put', (params, callback) => { callback(null, {}) }) @@ -70,7 +80,9 @@ prepare(function (done) { // Mock Posting to Bus API and ES interactions const authUrl = URL.parse(config.AUTH0_URL) const busUrl = URL.parse(config.BUSAPI_EVENTS_URL) - const challengeApiUrl = URL.parse(`${config.CHALLENGEAPI_URL}/30049360/phases`) + const challengeApiUrl = URL.parse(`${config.CHALLENGEAPI_V5_URL}/c3564180-65aa-42ec-a945-5fd21dec0502`) + const resourcesApi = URL.parse(`${config.BUSAPI_URL}/resources?challengeId=c3564180-65aa-42ec-a945-5fd21dec0502`) + const resourceRolesApi = URL.parse(`${config.BUSAPI_URL}/resource-roles`) nock(/.com/) .persist() @@ -91,11 +103,15 @@ prepare(function (done) { return body }) .post(authUrl.path) - .reply(200, { access_token: 'test' }) + .reply(200, { access_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci5jb20nIiwiaWF0IjoxNjEyMjg0NDIyLCJleHAiOjE2MTIyODgyODEsInVzZXJJZCI6IjQwNDMzMjg4IiwiZW1haWwiOiJhZG1pbkB0b3Bjb2Rlci5jb20iLCJqdGkiOiJjM2FjNjA4YS01NmJlLTQ1ZDAtOGY2YS0zMWZlOTRiOTU2MWMiLCJyb2xlcyI6IltcIkFkbWluaXN0cmF0b3JcIl0iLCJoYW5kbGUiOiJUb255SiJ9.7MLAeTtAxS-RvQWA2fEoS2va7mOLd_n-COnDWzLVQ_s' }) .post(busUrl.path) .reply(204) .get(challengeApiUrl.path) .reply(200, testData.testChallengeAPIResponse) + .get(resourcesApi.path) + .reply(200, testData.testChallengeResources) + .get(resourceRolesApi.path) + .reply(200, testData.testResourceRoles) .post(`/${config.esConfig.ES_INDEX}/${config.esConfig.ES_TYPE}/_search`, 'reviewType') .query(true) .reply(200, testData.testReviewTypesES)