From 7e1f9ef5b730a7fd18a3340ee17341d20c5f83f2 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Fri, 18 Sep 2020 19:14:26 +0530 Subject: [PATCH 01/31] #193 - Add new submittedDate and reviewedDate attributes to the models --- docs/swagger.yaml | 27 ++++++++++++++++++++ package-lock.json | 25 ++++++++++++++++-- scripts/createIndex.js | 2 +- scripts/data/Reviews.json | 4 +-- scripts/data/Submissions.json | 4 +-- src/services/ReviewService.js | 28 +++++++++++++++------ src/services/ReviewSummationService.js | 35 ++++++++++++++++++++------ src/services/SubmissionService.js | 32 +++++++++++++++++------ 8 files changed, 129 insertions(+), 28 deletions(-) diff --git a/docs/swagger.yaml b/docs/swagger.yaml index f41a9d40..abb1ff2f 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 @@ -931,6 +941,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 +953,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' @@ -1880,6 +1892,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 +1956,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 +2028,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 index fac7153a..e53ad15f 100755 --- 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", @@ -2727,6 +2735,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", @@ -4342,6 +4355,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 +4566,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", diff --git a/scripts/createIndex.js b/scripts/createIndex.js index e847fb3d..0780a041 100644 --- a/scripts/createIndex.js +++ b/scripts/createIndex.js @@ -17,7 +17,7 @@ co(function * createIndex () { // fields not specified below will be 'text' by default properties: { resource: { type: 'keyword' }, - challengeId: { type: 'long' }, + challengeId: { type: 'keyword' }, memberId: { type: 'keyword' }, type: { type: 'keyword' }, isFileSubmission: { type: 'boolean' }, 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/src/services/ReviewService.js b/src/services/ReviewService.js index a24ec142..7e7216e4 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') + } + } else { + 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..575be3df 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') + } + } else { + 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..44c523be 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -310,8 +310,13 @@ 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 @@ -363,7 +368,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() } @@ -398,14 +404,15 @@ function * _updateSubmission (authUser, submissionId, entity) { id: submissionId }, UpdateExpression: `set #type = :t, #url = :u, memberId = :m, challengeId = :c, - updated = :ua, updatedBy = :ub`, + 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, ':ua': currDate, - ':ub': authUser.handle || authUser.sub + ':ub': authUser.handle || authUser.sub, + ':sb': entity.submittedDate || exist.submittedDate || exist.created }, ExpressionAttributeNames: { '#type': 'type', @@ -449,7 +456,8 @@ function * _updateSubmission (authUser, submissionId, entity) { challengeId: updatedSub.challengeId, memberId: updatedSub.memberId, submissionPhaseId: updatedSub.submissionPhaseId, - type: updatedSub.type + type: updatedSub.type, + submittedDate: updatedSub.submittedDate }, entity) } @@ -458,7 +466,15 @@ 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 + } + ) } /** @@ -482,7 +498,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 +524,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() }) } From 26ffa9225b37e1d1b168f176b511caae5da98612 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Fri, 18 Sep 2020 19:39:20 +0530 Subject: [PATCH 02/31] #193 - housekeeping --- docs/swagger.yaml | 4 + package-lock.json | 1334 ++++++++++++++++++--------------------------- 2 files changed, 527 insertions(+), 811 deletions(-) mode change 100755 => 100644 package-lock.json diff --git a/docs/swagger.yaml b/docs/swagger.yaml index abb1ff2f..812af77a 100755 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -728,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 @@ -1050,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 diff --git a/package-lock.json b/package-lock.json old mode 100755 new mode 100644 index e53ad15f..628b94d1 --- a/package-lock.json +++ b/package-lock.json @@ -1017,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", @@ -2893,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", @@ -4763,7 +4219,8 @@ "dependencies": { "align-text": { "version": "0.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "optional": true, "requires": { @@ -4774,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" @@ -4792,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", @@ -4856,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" @@ -4864,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" @@ -4872,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" @@ -4880,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", @@ -4890,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", @@ -4906,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", @@ -4923,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" @@ -4933,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", @@ -4954,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", @@ -4964,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": { @@ -4980,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", @@ -4991,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" @@ -5001,7 +4487,8 @@ }, "cliui": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "dev": true, "optional": true, "requires": { @@ -5012,7 +4499,8 @@ "dependencies": { "wordwrap": { "version": "0.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", "dev": true, "optional": true } @@ -5020,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", @@ -5034,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", @@ -5068,7 +4564,8 @@ }, "debug": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -5076,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" @@ -5099,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", @@ -5108,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" @@ -5116,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" @@ -5124,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", @@ -5134,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" @@ -5149,7 +4656,8 @@ }, "execa": { "version": "0.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { "cross-spawn": "^5.0.1", @@ -5163,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", @@ -5175,7 +4684,8 @@ }, "expand-brackets": { "version": "2.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { "debug": "^2.3.3", @@ -5189,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" @@ -5197,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" @@ -5205,7 +4717,8 @@ }, "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -5215,7 +4728,8 @@ }, "extend-shallow": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { "assign-symbols": "^1.0.0", @@ -5224,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" @@ -5234,7 +4749,8 @@ }, "extglob": { "version": "2.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -5249,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" @@ -5257,7 +4774,8 @@ }, "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -5265,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" @@ -5273,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" @@ -5281,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", @@ -5291,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", @@ -5309,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" @@ -5319,7 +4843,8 @@ }, "find-cache-dir": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", "dev": true, "requires": { "commondir": "^1.0.1", @@ -5329,7 +4854,8 @@ }, "find-up": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { "locate-path": "^2.0.0" @@ -5337,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", @@ -5351,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" @@ -5359,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", @@ -5392,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", @@ -5408,7 +4944,8 @@ "dependencies": { "source-map": { "version": "0.4.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { "amdefine": ">=0.0.4" @@ -5418,7 +4955,8 @@ }, "has-value": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { "get-value": "^2.0.6", @@ -5428,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", @@ -5437,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" @@ -5447,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", @@ -5466,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" @@ -5484,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" @@ -5502,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" @@ -5510,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", @@ -5520,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" @@ -5545,7 +5100,8 @@ }, "is-odd": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-dkZiRnH9fqVYzNmieVGC8pWPGyQ=", "dev": true, "requires": { "is-number": "^4.0.0" @@ -5553,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" @@ -5568,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" @@ -5611,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", @@ -5622,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" @@ -5637,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", @@ -5649,7 +5219,8 @@ }, "istanbul-reports": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ty6OkoqnoF0dpsQn1AmLJlXsczQ=", "dev": true, "requires": { "handlebars": "^4.0.3" @@ -5657,7 +5228,8 @@ }, "kind-of": { "version": "3.2.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { "is-buffer": "^1.1.5" @@ -5665,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" @@ -5679,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", @@ -5691,7 +5266,8 @@ }, "locate-path": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { "p-locate": "^2.0.0", @@ -5700,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", @@ -5722,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" @@ -5735,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" @@ -5743,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" @@ -5756,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" @@ -5764,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", @@ -5791,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" @@ -5811,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", @@ -5825,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" @@ -5835,7 +5428,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -5843,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", @@ -5867,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", @@ -5885,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" @@ -5893,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", @@ -5913,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" @@ -5923,7 +5526,8 @@ }, "object-visit": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { "isobject": "^3.0.0" @@ -5931,7 +5535,8 @@ }, "object.pick": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { "isobject": "^3.0.1" @@ -5939,7 +5544,8 @@ }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -5947,7 +5553,8 @@ }, "optimist": { "version": "0.6.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { "minimist": "~0.0.1", @@ -5956,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", @@ -5971,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" @@ -5984,7 +5595,8 @@ }, "p-locate": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { "p-limit": "^1.1.0" @@ -5992,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" @@ -6005,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" @@ -6018,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", @@ -6043,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" @@ -6061,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" @@ -6069,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", @@ -6080,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", @@ -6100,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", @@ -6109,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", @@ -6120,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", @@ -6129,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": { @@ -6173,7 +5812,8 @@ }, "rimraf": { "version": "2.6.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", "dev": true, "requires": { "glob": "^7.0.5" @@ -6181,7 +5821,8 @@ }, "safe-regex": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { "ret": "~0.1.10" @@ -6189,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", @@ -6210,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" @@ -6220,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" @@ -6228,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", @@ -6258,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" @@ -6266,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" @@ -6274,7 +5926,8 @@ }, "extend-shallow": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" @@ -6284,7 +5937,8 @@ }, "snapdragon-node": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", "dev": true, "requires": { "define-property": "^1.0.0", @@ -6294,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" @@ -6302,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" @@ -6310,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" @@ -6318,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", @@ -6328,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" @@ -6343,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", @@ -6360,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", @@ -6378,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", @@ -6387,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", @@ -6401,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" @@ -6414,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", @@ -6423,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" @@ -6433,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", @@ -6442,7 +6114,8 @@ }, "strip-ansi": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -6450,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" @@ -6458,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", @@ -6475,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" @@ -6483,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", @@ -6494,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", @@ -6503,7 +6182,8 @@ }, "uglify-js": { "version": "2.8.29", - "bundled": true, + "resolved": false, + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "dev": true, "optional": true, "requires": { @@ -6514,7 +6194,8 @@ "dependencies": { "yargs": { "version": "3.10.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "optional": true, "requires": { @@ -6528,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", @@ -6545,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" @@ -6553,7 +6237,8 @@ }, "set-value": { "version": "0.4.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -6566,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", @@ -6575,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", @@ -6585,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" @@ -6595,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" @@ -6615,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", @@ -6631,7 +6324,8 @@ }, "which": { "version": "1.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", "dev": true, "requires": { "isexe": "^2.0.0" @@ -6639,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", @@ -6664,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" @@ -6677,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", @@ -6687,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" @@ -6697,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", @@ -6712,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", @@ -6741,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", @@ -6756,7 +6465,8 @@ }, "yargs-parser": { "version": "9.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { "camelcase": "^4.1.0" @@ -6766,7 +6476,8 @@ }, "yargs-parser": { "version": "8.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-8TdqM7Ziml0GN4KUTacyYx6WaVA=", "dev": true, "requires": { "camelcase": "^4.1.0" @@ -6774,7 +6485,8 @@ "dependencies": { "camelcase": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true } } From 0bde6336a957bac63cea19c33f0e902cafbae1a4 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Fri, 18 Sep 2020 20:44:00 +0530 Subject: [PATCH 03/31] #193 - Even for non admin users, set the new attribute value --- src/services/ReviewService.js | 4 ++-- src/services/ReviewSummationService.js | 4 ++-- src/services/SubmissionService.js | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/services/ReviewService.js b/src/services/ReviewService.js index 7e7216e4..ffd565d0 100644 --- a/src/services/ReviewService.js +++ b/src/services/ReviewService.js @@ -148,10 +148,10 @@ function * createReview (authUser, entity) { if (entity.reviewedDate) { throw new errors.HttpStatusError(403, 'You are not allowed to set the `reviewedDate` attribute on a review') } - } else { - item.reviewedDate = entity.reviewedDate || item.created } + item.reviewedDate = entity.reviewedDate || item.created + // Prepare record to be inserted const record = { TableName: table, diff --git a/src/services/ReviewSummationService.js b/src/services/ReviewSummationService.js index 575be3df..69af4aa2 100644 --- a/src/services/ReviewSummationService.js +++ b/src/services/ReviewSummationService.js @@ -106,10 +106,10 @@ function * createReviewSummation (authUser, entity) { if (entity.reviewedDate) { throw new errors.HttpStatusError(403, 'You are not allowed to set the `reviewedDate` attribute on a review summation') } - } else { - item.reviewedDate = entity.reviewedDate || item.created } + item.reviewedDate = entity.reviewedDate || item.created + const record = { TableName: table, Item: item diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index 44c523be..efa4a4e9 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -316,9 +316,10 @@ function * createSubmission (authUser, files, entity) { } } else { logger.info(`No need to call checkCreateAccess for ${JSON.stringify(authUser)}`) - item.submittedDate = entity.submittedDate || item.created } + item.submittedDate = entity.submittedDate || item.created + // Prepare record to be inserted const record = { TableName: table, From 36b8917371b598fbf49457423f6048e6a4c379bf Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Wed, 23 Sep 2020 10:41:07 +0530 Subject: [PATCH 04/31] Trigger CI build --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 30b12d50..e2dcdfcd 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 From ed5a40df7298623ccfb248f2d2cbc673ffc01f14 Mon Sep 17 00:00:00 2001 From: James Cori Date: Wed, 23 Sep 2020 11:56:31 -0400 Subject: [PATCH 05/31] Removing health check --- src/services/HealthCheckService.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/services/HealthCheckService.js b/src/services/HealthCheckService.js index 03e4a5e4..fdd0a87a 100644 --- a/src/services/HealthCheckService.js +++ b/src/services/HealthCheckService.js @@ -2,23 +2,24 @@ * Health Check Service */ -const errors = require('common-errors') -const helper = require('../common/helper') +// const errors = require('common-errors') +// const helper = require('../common/helper') /** * Check if the elasticsearch connection is active */ function * check () { - const esClient = helper.getEsClient() + // const esClient = helper.getEsClient() - try { - yield esClient.ping({ - requestTimeout: 10000 - }) - } catch (e) { - throw new errors.HttpStatusError(503, 'Elasticsearch instance cannot be reached') - } + // try { + // yield esClient.ping({ + // requestTimeout: 10000 + // }) + + // } catch (e) { + // throw new errors.HttpStatusError(503, 'Elasticsearch instance cannot be reached') + // } return { checksRun: 1 From fbdd04f4f023281ee9f8b3e8f067a51b949d024f Mon Sep 17 00:00:00 2001 From: CWD Date: Wed, 23 Sep 2020 18:13:29 -0400 Subject: [PATCH 06/31] updating deploy script version --- .circleci/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cee39cfd..f80874d6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,13 +13,14 @@ install_dependency: &install_dependency 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 + apk add py-pip python-dev libffi-dev openssl-dev gcc libc-dev make sudo pip install docker-compose sudo chmod +x /usr/local/bin/ecs-cli 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 +80,4 @@ workflows: context : org-global filters: branches: - only: master \ No newline at end of file + only: master From c502781fe107c527b8975a0ed0bb4085cc79706a Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Tue, 27 Oct 2020 20:45:06 +0530 Subject: [PATCH 07/31] Fix issue where unregistered members could submit for a challenge --- src/common/helper.js | 49 +++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index bf472f73..0cd32dae 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -367,30 +367,49 @@ function * getSubmissionPhaseId (challengeId) { * @returns {Promise} */ function * checkCreateAccess (authUser, subEntity) { - let response + let challengeDetails + let resources // 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}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${subEntity.challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') - logger.info(`returned for ${subEntity.challengeId} with ${JSON.stringify(response)}`) + logger.info(`returned for ${subEntity.challengeId} with ${JSON.stringify(challengeDetails)}`) } catch (ex) { logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}?filter=id=${subEntity.challengeId}`) logger.error(ex) - return false + throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${subEntity.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.CHALLENGEAPI_URL}/${subEntity.challengeId}/resources`) + .set('Authorization', `Bearer ${token}`) + .set('Content-Type', 'application/json') + } catch (ex) { + logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}/${subEntity.challengeId}/resources`) + logger.error(ex) + 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.result.content, { properties: { Handle: authUser.handle } }) + // Get phases and winner detail from challengeDetails + const phases = challengeDetails.body.result.content[0].allPhases + const winner = challengeDetails.body.result.content[0].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) @@ -405,9 +424,11 @@ function * checkCreateAccess (authUser, subEntity) { 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 } /* @@ -433,7 +454,7 @@ function * checkGetAccess (authUser, submission) { } catch (ex) { logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}/${submission.challengeId}/resources`) logger.error(ex) - return false + throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${submission.challengeId}`) } try { @@ -443,7 +464,7 @@ function * checkGetAccess (authUser, submission) { } catch (ex) { logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) logger.error(ex) - return false + throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${submission.challengeId}`) } if (resources && challengeDetails) { @@ -509,7 +530,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}`) } } From f938a7da49bb5bb56220f00f4f9e6e49e1eeb32b Mon Sep 17 00:00:00 2001 From: Gunasekar-K Date: Fri, 30 Oct 2020 13:23:19 +0530 Subject: [PATCH 08/31] Update config.yml --- .circleci/config.yml | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f80874d6..d71b5cf1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,21 +1,13 @@ 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 - apk add py-pip python-dev libffi-dev openssl-dev gcc libc-dev make - 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. From 24e23072ca175662748cc04ae5d0dcf8638c81dc Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Fri, 29 Jan 2021 12:38:27 +0530 Subject: [PATCH 09/31] Undo changes of commit https://github.com/topcoder-platform/submissions-api/commit/ed5a40df7298623ccfb248f2d2cbc673ffc01f14 --- src/services/HealthCheckService.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/services/HealthCheckService.js b/src/services/HealthCheckService.js index fdd0a87a..03e4a5e4 100644 --- a/src/services/HealthCheckService.js +++ b/src/services/HealthCheckService.js @@ -2,24 +2,23 @@ * Health Check Service */ -// const errors = require('common-errors') -// const helper = require('../common/helper') +const errors = require('common-errors') +const helper = require('../common/helper') /** * Check if the elasticsearch connection is active */ function * check () { - // const esClient = helper.getEsClient() + const esClient = helper.getEsClient() - // try { - // yield esClient.ping({ - // requestTimeout: 10000 - // }) - - // } catch (e) { - // throw new errors.HttpStatusError(503, 'Elasticsearch instance cannot be reached') - // } + try { + yield esClient.ping({ + requestTimeout: 10000 + }) + } catch (e) { + throw new errors.HttpStatusError(503, 'Elasticsearch instance cannot be reached') + } return { checksRun: 1 From c0bb93b7cd6159e0f70d511ea3139cedb5234a6e Mon Sep 17 00:00:00 2001 From: Sandeep Date: Mon, 1 Feb 2021 17:10:33 +0530 Subject: [PATCH 10/31] migration to v5 --- config/default.js | 2 +- config/test.js | 2 +- src/common/helper.js | 186 ++++++++++++++++++++---------- src/services/SubmissionService.js | 20 +--- test/unit/prepare.js | 2 +- 5 files changed, 133 insertions(+), 79 deletions(-) diff --git a/config/default.js b/config/default.js index aaf4412b..5ba2d291 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, diff --git a/config/test.js b/config/test.js index c702b1f7..fde3087d 100644 --- a/config/test.js +++ b/config/test.js @@ -16,7 +16,7 @@ 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', + 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 diff --git a/src/common/helper.js b/src/common/helper.js index 0cd32dae..5b7059ee 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -299,30 +299,6 @@ function * getM2Mtoken () { return yield m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) } -/** - * Get legacy challenge id if the challenge id is uuid form - * @param {String} challengeId Challenge ID - * @returns {String} Legacy Challenge ID of the given challengeId - */ -function * getLegacyChallengeId (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 uuid. Fetching legacy challenge id`) - const token = yield getM2Mtoken() - try { - const response = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) - .set('Authorization', `Bearer ${token}`) - .set('Content-Type', 'application/json') - const legacyId = parseInt(response.body.legacyId, 10) - logger.debug(`Legacy challenge id is ${legacyId} for v5 challenge id ${challengeId}`) - return legacyId - } catch (err) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) - throw err - } - } - return challengeId -} - /* * Get submission phase ID of a challenge from Challenge API * @param challengeId Challenge ID @@ -335,20 +311,20 @@ function * getSubmissionPhaseId (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) { @@ -379,31 +355,45 @@ function * checkCreateAccess (authUser, subEntity) { try { logger.info(`Calling to challenge API for fetch phases and winners for ${subEntity.challengeId}`) - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${subEntity.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${subEntity.challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') logger.info(`returned for ${subEntity.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}/${subEntity.challengeId}`) logger.error(ex) throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${subEntity.challengeId}`) } try { - resources = yield request.get(`${config.CHALLENGEAPI_URL}/${subEntity.challengeId}/resources`) + resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${subEntity.challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}/${subEntity.challengeId}/resources`) + logger.error(`Error while accessing ${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${subEntity.challengeId}`) logger.error(ex) throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${subEntity.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.result.content, { properties: { Handle: authUser.handle } }) + 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.result.content[0].allPhases - const winner = challengeDetails.body.result.content[0].winners + 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' }) @@ -419,7 +409,7 @@ 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') } @@ -448,30 +438,43 @@ function * checkGetAccess (authUser, submission) { const token = yield getM2Mtoken() try { - resources = yield request.get(`${config.CHALLENGEAPI_URL}/${submission.challengeId}/resources`) + resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${submission.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=${submission.challengeId}`) logger.error(ex) throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${submission.challengeId}`) } try { - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${submission.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}/${submission.challengeId}`) logger.error(ex) throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${submission.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 ${submission.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' }) @@ -494,18 +497,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({ @@ -545,28 +548,27 @@ function * checkReviewGetAccess (authUser, submission) { const token = yield getM2Mtoken() try { - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${submission.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}/${submission.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') } @@ -642,6 +644,74 @@ 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.phases + if (challengeDetails.status === 'Completed') { + return 'Closed' + } else if (challengeDetails.status === 'Active') { + 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' + } + + // Search for phase where isOpen == true from list of phases + // Phases are already in sorted order as per challenge-api repository + const currentOpenPhaseIndex = _.findLastIndex(phases, phase => { + return phase.isOpen === true + }) + + // if queried phase occurs before current open phase it is 'Closed' + // else it is 'Scheduled' + if (currentOpenPhaseIndex !== -1) { + return currentOpenPhaseIndex > queriedPhaseIndex ? 'Closed' : 'Scheduled' + } else { + // if no phase is open but the challenge is Active return Scheduled + return 'Scheduled' + } + } else { // if challenge is not in Active or Completed state return Scheduled + return 'Scheduled' + } +} + module.exports = { wrapExpress, autoWrapExpress, @@ -649,12 +719,12 @@ module.exports = { fetchFromES, camelize, setPaginationHeaders, - getLegacyChallengeId, getSubmissionPhaseId, checkCreateAccess, checkGetAccess, checkReviewGetAccess, downloadFile, postToBusApi, - cleanseReviews + cleanseReviews, + getRoleIdToRoleNameMap } diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index efa4a4e9..66291545 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -181,12 +181,6 @@ function * downloadSubmission (authUser, submissionId) { * @return {Object} Data fetched from ES */ 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) - } - const data = yield helper.fetchFromES(query, helper.camelize(table)) logger.info(`listSubmissions: returning ${data.length} submissions for query: ${JSON.stringify(query)}`) @@ -268,10 +262,6 @@ function * createSubmission (authUser, files, entity) { throw new errors.HttpStatusError(400, 'The file should be uploaded under the "submission" attribute') } - // 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 currDate = (new Date()).toISOString() const item = { @@ -279,7 +269,7 @@ function * createSubmission (authUser, files, entity) { type: entity.type, url: url, memberId: entity.memberId, - challengeId: challengeId, + challengeId: entity.challengeId, created: currDate, updated: currDate, createdBy: authUser.handle || authUser.sub, @@ -297,7 +287,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) { @@ -391,12 +381,6 @@ function * _updateSubmission (authUser, submissionId, entity) { throw new errors.HttpStatusError(404, `Submission with ID = ${submissionId} is not found`) } - 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) - } - const currDate = (new Date()).toISOString() // Record used for updating in Database const record = { diff --git a/test/unit/prepare.js b/test/unit/prepare.js index 824420db..300ace33 100644 --- a/test/unit/prepare.js +++ b/test/unit/prepare.js @@ -70,7 +70,7 @@ 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}/30049360/phases`) nock(/.com/) .persist() From 05a464aae64e464e3c2edb0e6568a5a255d89e8c Mon Sep 17 00:00:00 2001 From: Sachin Maheshwari Date: Wed, 3 Feb 2021 11:10:09 +0530 Subject: [PATCH 11/31] just verfiying the branch on dev env --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d71b5cf1..47bba1e3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,7 @@ workflows: context : org-global filters: branches: - only: ['develop'] + only: ['develop', 'develop-temp'] - "build-prod": context : org-global filters: From a0a788bc67849587d1704866357341721e46031d Mon Sep 17 00:00:00 2001 From: mark-nakachon Date: Wed, 3 Feb 2021 23:16:42 +0530 Subject: [PATCH 12/31] migration to v5 API --- config/default.js | 2 +- config/test.js | 8 +- src/common/helper.js | 180 ++++++++----- src/services/SubmissionService.js | 28 +- test/common/testData.js | 318 ++++++++++++++++------- test/unit/ReviewService.test.js | 5 +- test/unit/ReviewSummationService.test.js | 2 +- test/unit/ReviewTypeService.test.js | 5 +- test/unit/SubmissionService.test.js | 56 ++-- test/unit/prepare.js | 20 +- 10 files changed, 408 insertions(+), 216 deletions(-) diff --git a/config/default.js b/config/default.js index aaf4412b..5ba2d291 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, 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/src/common/helper.js b/src/common/helper.js index 0cd32dae..5289ea67 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 } @@ -299,30 +299,6 @@ function * getM2Mtoken () { return yield m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) } -/** - * Get legacy challenge id if the challenge id is uuid form - * @param {String} challengeId Challenge ID - * @returns {String} Legacy Challenge ID of the given challengeId - */ -function * getLegacyChallengeId (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 uuid. Fetching legacy challenge id`) - const token = yield getM2Mtoken() - try { - const response = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) - .set('Authorization', `Bearer ${token}`) - .set('Content-Type', 'application/json') - const legacyId = parseInt(response.body.legacyId, 10) - logger.debug(`Legacy challenge id is ${legacyId} for v5 challenge id ${challengeId}`) - return legacyId - } catch (err) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) - throw err - } - } - return challengeId -} - /* * Get submission phase ID of a challenge from Challenge API * @param challengeId Challenge ID @@ -335,20 +311,20 @@ function * getSubmissionPhaseId (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) { @@ -379,31 +355,45 @@ function * checkCreateAccess (authUser, subEntity) { try { logger.info(`Calling to challenge API for fetch phases and winners for ${subEntity.challengeId}`) - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${subEntity.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${subEntity.challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') logger.info(`returned for ${subEntity.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}/${subEntity.challengeId}`) logger.error(ex) throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${subEntity.challengeId}`) } try { - resources = yield request.get(`${config.CHALLENGEAPI_URL}/${subEntity.challengeId}/resources`) + resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${subEntity.challengeId}`) .set('Authorization', `Bearer ${token}`) .set('Content-Type', 'application/json') } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_URL}/${subEntity.challengeId}/resources`) + logger.error(`Error while accessing ${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${subEntity.challengeId}`) logger.error(ex) throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${subEntity.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.result.content, { properties: { Handle: authUser.handle } }) + 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.result.content[0].allPhases - const winner = challengeDetails.body.result.content[0].winners + 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' }) @@ -419,7 +409,7 @@ 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') } @@ -448,30 +438,43 @@ function * checkGetAccess (authUser, submission) { const token = yield getM2Mtoken() try { - resources = yield request.get(`${config.CHALLENGEAPI_URL}/${submission.challengeId}/resources`) + resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${submission.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=${submission.challengeId}`) logger.error(ex) throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${submission.challengeId}`) } try { - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${submission.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}/${submission.challengeId}`) logger.error(ex) throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${submission.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 ${submission.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' }) @@ -494,18 +497,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({ @@ -545,28 +548,27 @@ function * checkReviewGetAccess (authUser, submission) { const token = yield getM2Mtoken() try { - challengeDetails = yield request.get(`${config.CHALLENGEAPI_URL}?filter=id=${submission.challengeId}`) + challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${submission.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}/${submission.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') } @@ -642,6 +644,66 @@ 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' + } + } +} + module.exports = { wrapExpress, autoWrapExpress, @@ -649,12 +711,12 @@ module.exports = { fetchFromES, camelize, setPaginationHeaders, - getLegacyChallengeId, getSubmissionPhaseId, checkCreateAccess, checkGetAccess, checkReviewGetAccess, downloadFile, postToBusApi, - cleanseReviews + cleanseReviews, + getRoleIdToRoleNameMap } diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index efa4a4e9..20c323dc 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -181,12 +181,6 @@ function * downloadSubmission (authUser, submissionId) { * @return {Object} Data fetched from ES */ 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) - } - const data = yield helper.fetchFromES(query, helper.camelize(table)) logger.info(`listSubmissions: returning ${data.length} submissions for query: ${JSON.stringify(query)}`) @@ -203,7 +197,7 @@ const listSubmissionsQuerySchema = { type: joi.string(), url: joi.string().uri().trim(), memberId: joi.alternatives().try(joi.id(), joi.string().uuid()), - challengeId: joi.alternatives().try(joi.id(), joi.string().uuid()), + challengeId: joi.string().uuid(), legacySubmissionId: joi.alternatives().try(joi.id(), joi.string().uuid()), legacyUploadId: joi.alternatives().try(joi.id(), joi.string().uuid()), submissionPhaseId: joi.id(), @@ -268,10 +262,6 @@ function * createSubmission (authUser, files, entity) { throw new errors.HttpStatusError(400, 'The file should be uploaded under the "submission" attribute') } - // 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 currDate = (new Date()).toISOString() const item = { @@ -279,7 +269,7 @@ function * createSubmission (authUser, files, entity) { type: entity.type, url: url, memberId: entity.memberId, - challengeId: challengeId, + challengeId: entity.challengeId, created: currDate, updated: currDate, createdBy: authUser.handle || authUser.sub, @@ -297,7 +287,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) { @@ -366,7 +356,7 @@ createSubmission.schema = { fileType: joi.string(), url: joi.string().uri().trim(), memberId: joi.alternatives().try(joi.id(), joi.string().uuid()).required(), - challengeId: joi.alternatives().try(joi.id(), joi.string().uuid()).required(), + challengeId: 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(), @@ -391,12 +381,6 @@ function * _updateSubmission (authUser, submissionId, entity) { throw new errors.HttpStatusError(404, `Submission with ID = ${submissionId} is not found`) } - 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) - } - const currDate = (new Date()).toISOString() // Record used for updating in Database const record = { @@ -496,7 +480,7 @@ updateSubmission.schema = { type: joi.string(), url: joi.string().uri().trim().required(), memberId: joi.alternatives().try(joi.id(), joi.string().uuid()).required(), - challengeId: joi.alternatives().try(joi.id(), joi.string().uuid()).required(), + challengeId: 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(), @@ -522,7 +506,7 @@ patchSubmission.schema = { type: joi.string(), url: joi.string().uri().trim(), memberId: joi.alternatives().try(joi.id(), joi.string().uuid()), - challengeId: joi.alternatives().try(joi.id(), joi.string().uuid()), + challengeId: joi.string().uuid(), legacySubmissionId: joi.alternatives().try(joi.id(), joi.string().uuid()), legacyUploadId: joi.alternatives().try(joi.id(), joi.string().uuid()), submissionPhaseId: joi.id(), 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) From 294df4547b9ff130d425c6d1132082e796c698de Mon Sep 17 00:00:00 2001 From: Sachin Maheshwari Date: Fri, 5 Feb 2021 22:33:52 +0530 Subject: [PATCH 13/31] reverted to fetch legacy-id code --- src/common/helper.js | 24 ++++++++++++++++++++++++ src/services/SubmissionService.js | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/src/common/helper.js b/src/common/helper.js index 5289ea67..4ae7ba77 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -299,6 +299,30 @@ function * getM2Mtoken () { return yield m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) } +/** + * Get legacy challenge id if the challenge id is uuid form + * @param {String} challengeId Challenge ID + * @returns {String} Legacy Challenge ID of the given challengeId + */ +function * getLegacyChallengeId (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 uuid. Fetching legacy challenge id`) + const token = yield getM2Mtoken() + try { + const response = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) + .set('Authorization', `Bearer ${token}`) + .set('Content-Type', 'application/json') + const legacyId = parseInt(response.body.legacyId, 10) + logger.debug(`Legacy challenge id is ${legacyId} for v5 challenge id ${challengeId}`) + return legacyId + } catch (err) { + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) + throw err + } + } + return challengeId +} + /* * Get submission phase ID of a challenge from Challenge API * @param challengeId Challenge ID diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index 20c323dc..3a3c9602 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -181,6 +181,12 @@ function * downloadSubmission (authUser, submissionId) { * @return {Object} Data fetched from ES */ 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) + } + const data = yield helper.fetchFromES(query, helper.camelize(table)) logger.info(`listSubmissions: returning ${data.length} submissions for query: ${JSON.stringify(query)}`) From a6ee3c0dec4f2be990b0fc13fb66b37f56f5e7cd Mon Sep 17 00:00:00 2001 From: Sachin Maheshwari Date: Fri, 5 Feb 2021 22:36:11 +0530 Subject: [PATCH 14/31] deploying feature/shapeup-pure-v5-task branch for testing --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 47bba1e3..e8eb4a18 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'develop-temp'] + only: ['develop', 'feature/shapeup-pure-v5-task'] - "build-prod": context : org-global filters: From 03c56a6f0afaaa1628c0e26828d1d6f72062822a Mon Sep 17 00:00:00 2001 From: Sachin Maheshwari Date: Fri, 5 Feb 2021 22:42:33 +0530 Subject: [PATCH 15/31] typo --- src/common/helper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/helper.js b/src/common/helper.js index 4ae7ba77..0c013a45 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -735,6 +735,7 @@ module.exports = { fetchFromES, camelize, setPaginationHeaders, + getLegacyChallengeId, getSubmissionPhaseId, checkCreateAccess, checkGetAccess, From 5c83c09e7db4a475c633ddd3c8b817e9637edcb1 Mon Sep 17 00:00:00 2001 From: Sachin Maheshwari Date: Fri, 5 Feb 2021 23:22:03 +0530 Subject: [PATCH 16/31] reverting schema validation too --- src/services/SubmissionService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index 3a3c9602..0de1118b 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -203,7 +203,7 @@ const listSubmissionsQuerySchema = { type: joi.string(), url: joi.string().uri().trim(), memberId: joi.alternatives().try(joi.id(), joi.string().uuid()), - challengeId: joi.string().uuid(), + 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(), From fc9184dc711cf7d6e0ed883f93569cfac6d2d6c0 Mon Sep 17 00:00:00 2001 From: Sachin Maheshwari Date: Sun, 7 Feb 2021 21:45:36 +0530 Subject: [PATCH 17/31] getting v5 challenge id from legeacy id --- src/common/helper.js | 103 +++++++++++++++++++----------- src/services/SubmissionService.js | 5 +- 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 0c013a45..4531fe63 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -31,8 +31,8 @@ let busApiClient * @param {Function} fn the generator function * @returns {Function} the wrapped function */ -function wrapExpress (fn) { - return function wrap (req, res, next) { +function wrapExpress(fn) { + return function wrap(req, res, next) { co(fn(req, res, next)).catch(next) } } @@ -42,7 +42,7 @@ function wrapExpress (fn) { * @param obj the object (controller exports) * @returns {Object|Array} the wrapped object */ -function autoWrapExpress (obj) { +function autoWrapExpress(obj) { if (_.isArray(obj)) { return obj.map(autoWrapExpress) } @@ -62,7 +62,7 @@ function autoWrapExpress (obj) { * Get Bus API Client * @return {Object} Bus API Client Instance */ -function getBusApiClient () { +function getBusApiClient() { // If there is no Client instance, Create a new instance if (!busApiClient) { logger.debug(`Creating Bus API client for ${config.BUSAPI_URL} `) @@ -80,7 +80,7 @@ function getBusApiClient () { * Get ES Client * @return {Object} Elastic Host Client Instance */ -function getEsClient () { +function getEsClient() { const esHost = config.get('esConfig.HOST') if (!esClients.client) { // AWS ES configuration is different from other providers @@ -109,7 +109,7 @@ function getEsClient () { * @param str Input string * @returns string String converted into camelCase */ -function camelize (str) { +function camelize(str) { return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match, index) { if (+match === 0) return '' // or if (/\s+/.test(match)) for white spaces return index === 0 ? match.toLowerCase() : match.toUpperCase() @@ -122,7 +122,7 @@ function camelize (str) { * @param {String} actResource Resource name in ES * @return {Object} search request body that can be passed to ES */ -function prepESFilter (query, actResource) { +function prepESFilter(query, actResource) { const pageSize = query.perPage || config.get('PAGE_SIZE') const page = query.page || 1 const { sortBy, orderBy } = query @@ -220,7 +220,7 @@ function prepESFilter (query, actResource) { * @param {String} resource Resource name in ES * @return {Object} Data fetched from ES based on the filters */ -function * fetchFromES (query, resource) { +function* fetchFromES(query, resource) { const esClient = getEsClient() // Construct ES filter const filter = prepESFilter(query, resource) @@ -244,7 +244,7 @@ function * fetchFromES (query, resource) { * @param res HTTP response * @param {Object} data Data for which pagination need to be applied */ -function setPaginationHeaders (req, res, data) { +function setPaginationHeaders(req, res, data) { const totalPages = Math.ceil(data.total / data.pageSize) let fullUrl = req.protocol + '://' + req.get('host') + req.url.replace(`&page=${data.page}`, '') // URL formatting to add pagination parameters accordingly @@ -295,7 +295,7 @@ function setPaginationHeaders (req, res, data) { /* Function to get M2M token * @returns {Promise} */ -function * getM2Mtoken () { +function* getM2Mtoken() { return yield m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) } @@ -304,7 +304,7 @@ function * getM2Mtoken () { * @param {String} challengeId Challenge ID * @returns {String} Legacy Challenge ID of the given challengeId */ -function * getLegacyChallengeId (challengeId) { +function* getLegacyChallengeId(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 uuid. Fetching legacy challenge id`) const token = yield getM2Mtoken() @@ -323,14 +323,39 @@ 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.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 * @returns {Integer} Submission phase ID of the given challengeId */ -function * getSubmissionPhaseId (challengeId) { +function* getSubmissionPhaseId(challengeId) { let phaseId = null let response + challengeId = getV5ChallengeId(challengeId) try { logger.info(`Calling to challenge API to find submission phase Id for ${challengeId}`) @@ -366,10 +391,12 @@ function * getSubmissionPhaseId (challengeId) { * @param subEntity Submission Entity * @returns {Promise} */ -function * checkCreateAccess (authUser, subEntity) { +function* checkCreateAccess(authUser, subEntity) { let challengeDetails let resources + const challengeId = 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') @@ -378,25 +405,25 @@ function * checkCreateAccess (authUser, subEntity) { const token = yield getM2Mtoken() try { - logger.info(`Calling to challenge API for fetch phases and winners for ${subEntity.challengeId}`) - challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${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(challengeDetails)}`) + logger.info(`returned for ${challengeId} with ${JSON.stringify(challengeDetails)}`) } catch (ex) { - logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${subEntity.challengeId}`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.error(ex) - throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${subEntity.challengeId}`) + throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${challengeId}`) } try { - resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${subEntity.challengeId}`) + 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=${subEntity.challengeId}`) + 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 ${subEntity.challengeId}`) + 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 @@ -451,7 +478,7 @@ function * checkCreateAccess (authUser, subEntity) { * @param submission Submission Entity * @returns {Promise} */ -function * checkGetAccess (authUser, submission) { +function* checkGetAccess(authUser, submission) { let resources let challengeDetails // Allow downloading Own submission @@ -460,25 +487,26 @@ function * checkGetAccess (authUser, submission) { } const token = yield getM2Mtoken() + const challengeId = getV5ChallengeId(submission.challengeId) try { - resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${submission.challengeId}`) + 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=${submission.challengeId}`) + 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 ${submission.challengeId}`) + 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_V5_URL}/${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_V5_URL}/${submission.challengeId}`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.error(ex) - throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${submission.challengeId}`) + throw new errors.HttpStatusError(503, `Could not fetch details of challenge with id ${challengeId}`) } // Get map of role id to role name @@ -486,7 +514,7 @@ function * checkGetAccess (authUser, submission) { // 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 ${submission.challengeId}`) + throw new errors.HttpStatusError(503, `Could not determine the user's role in the challenge with id ${challengeId}`) } if (resources && challengeDetails) { @@ -567,16 +595,17 @@ function * checkGetAccess (authUser, submission) { * @param submission Submission Entity * @returns {Promise} */ -function * checkReviewGetAccess (authUser, submission) { +function* checkReviewGetAccess(authUser, submission) { let challengeDetails const token = yield getM2Mtoken() + const challengeId = getV5ChallengeId(submission.challengeId) try { - challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${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_V5_URL}/${submission.challengeId}`) + logger.error(`Error while accessing ${config.CHALLENGEAPI_V5_URL}/${challengeId}`) logger.error(ex) return false } @@ -606,7 +635,7 @@ function * checkReviewGetAccess (authUser, submission) { * @param{String} fileURL S3 URL of the file to be downloaded * @returns {Buffer} Buffer of downloaded file */ -function * downloadFile (fileURL) { +function* downloadFile(fileURL) { const { bucket, key } = AmazonS3URI(fileURL) logger.info(`downloadFile(): file is on S3 ${bucket} / ${key}`) const downloadedFile = yield s3.getObject({ Bucket: bucket, Key: key }).promise() @@ -619,7 +648,7 @@ function * downloadFile (fileURL) { * Also stores the original topic in the payload * @param {Object} payload Data that needs to be posted to the bus api */ -function * postToBusApi (payload) { +function* postToBusApi(payload) { const busApiClient = getBusApiClient() const originalTopic = payload.topic @@ -639,7 +668,7 @@ function * postToBusApi (payload) { * @param {Array} reviews The reviews to remove metadata from * @param {Object} authUser The authenticated user details */ -function cleanseReviews (reviews, authUser) { +function cleanseReviews(reviews, authUser) { // Not a machine user if (!authUser.scopes) { const admin = _.filter(authUser.roles, role => role.toLowerCase() === 'Administrator'.toLowerCase()) @@ -672,7 +701,7 @@ function cleanseReviews (reviews, authUser) { * Function to get role id to role name map * @returns {Object|null} map */ -function * getRoleIdToRoleNameMap () { +function* getRoleIdToRoleNameMap() { let resourceRoles let resourceRolesMap = null const token = yield getM2Mtoken() @@ -700,7 +729,7 @@ function * getRoleIdToRoleNameMap () { * @param {Object} challengeDetails the challenge details * @returns {('Scheduled' | 'Open' | 'Closed' | 'Invalid')} status of the phase */ -function getPhaseStatus (phaseName, challengeDetails) { +function getPhaseStatus(phaseName, challengeDetails) { const { phases } = challengeDetails const queriedPhaseIndex = _.findIndex(phases, phase => { return phase.name === phaseName diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index 0de1118b..c96c493f 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -268,6 +268,9 @@ function * createSubmission (authUser, files, entity) { throw new errors.HttpStatusError(400, 'The file should be uploaded under the "submission" attribute') } + // 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 currDate = (new Date()).toISOString() const item = { @@ -275,7 +278,7 @@ function * createSubmission (authUser, files, entity) { type: entity.type, url: url, memberId: entity.memberId, - challengeId: entity.challengeId, + challengeId: challengeId, created: currDate, updated: currDate, createdBy: authUser.handle || authUser.sub, From 8a1d6e4d160a08b1ef9be77e2344683f7504bf80 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Wed, 10 Feb 2021 12:15:30 +0530 Subject: [PATCH 18/31] #209 - npm script to store v5 challenge id for submissions --- README.md | 18 ++++++++ config/default.js | 2 + package.json | 1 + scripts/updateToV5ChallengeId.js | 78 ++++++++++++++++++++++++++++++++ src/common/helper.js | 47 +++++++++---------- 5 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 scripts/updateToV5ChallengeId.js diff --git a/README.md b/README.md index e2dcdfcd..75723530 100755 --- a/README.md +++ b/README.md @@ -171,6 +171,24 @@ 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 +``` + + +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 5ba2d291..cfef076f 100755 --- a/config/default.js +++ b/config/default.js @@ -37,5 +37,7 @@ 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, + UPDATE_V5_CHALLENGE_BATCH_SIZE: process.env.UPDATE_V5_CHALLENGE_BATCH_SIZE || 250, + SUBMISSION_TABLE_NAME: process.env.SUBMISSION_TABLE_NAME || 'Submission', AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL } diff --git a/package.json b/package.json index b09e8038..14ec3b52 100755 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "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", "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/updateToV5ChallengeId.js b/scripts/updateToV5ChallengeId.js new file mode 100644 index 00000000..b12ace12 --- /dev/null +++ b/scripts/updateToV5ChallengeId.js @@ -0,0 +1,78 @@ +/** + * 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 + * @returns {Promise} + */ +function * updateRecord (submission) { + const v5challengeId = yield helper.getV5ChallengeId(submission.challengeId) + const record = { + TableName: 'Submission', + Key: { + id: submission.id + }, + UpdateExpression: `set challengeId = :c, legacyChallengeId = :l`, + ExpressionAttributeValues: { + ':c': v5challengeId, + ':l': submission.challengeId + } + } + if (!v5challengeId) { + logger.warn(`the challengeId: ${submission.challengeId} is not having a v5 challengeId`) + + return + } else if (v5challengeId === submission.challengeId) { + logger.info(`the challengeId: ${submission.challengeId} is already a v5 challengeId`) + } + + yield dbhelper.updateRecord(record) +} + +/* + * Update all submission's challenge id to v5 + * @returns {Promise} + */ +function * updateRecords () { + const tableName = config.SUBMISSION_TABLE_NAME + let promises = [] + const params = { + TableName: 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}. More fetch iterations may follow (pagination in progress)`) + for (let i = 0; i < totalRecords; i++) { + const record = records.Items[i] + promises.push(updateRecord(record)) + } + // 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 + } +} + +co(function * () { + yield updateRecords() +}).catch((err) => { + logger.logFullError(err) +}) diff --git a/src/common/helper.js b/src/common/helper.js index 4531fe63..9e769bfc 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -31,8 +31,8 @@ let busApiClient * @param {Function} fn the generator function * @returns {Function} the wrapped function */ -function wrapExpress(fn) { - return function wrap(req, res, next) { +function wrapExpress (fn) { + return function wrap (req, res, next) { co(fn(req, res, next)).catch(next) } } @@ -42,7 +42,7 @@ function wrapExpress(fn) { * @param obj the object (controller exports) * @returns {Object|Array} the wrapped object */ -function autoWrapExpress(obj) { +function autoWrapExpress (obj) { if (_.isArray(obj)) { return obj.map(autoWrapExpress) } @@ -62,7 +62,7 @@ function autoWrapExpress(obj) { * Get Bus API Client * @return {Object} Bus API Client Instance */ -function getBusApiClient() { +function getBusApiClient () { // If there is no Client instance, Create a new instance if (!busApiClient) { logger.debug(`Creating Bus API client for ${config.BUSAPI_URL} `) @@ -80,7 +80,7 @@ function getBusApiClient() { * Get ES Client * @return {Object} Elastic Host Client Instance */ -function getEsClient() { +function getEsClient () { const esHost = config.get('esConfig.HOST') if (!esClients.client) { // AWS ES configuration is different from other providers @@ -109,7 +109,7 @@ function getEsClient() { * @param str Input string * @returns string String converted into camelCase */ -function camelize(str) { +function camelize (str) { return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match, index) { if (+match === 0) return '' // or if (/\s+/.test(match)) for white spaces return index === 0 ? match.toLowerCase() : match.toUpperCase() @@ -122,7 +122,7 @@ function camelize(str) { * @param {String} actResource Resource name in ES * @return {Object} search request body that can be passed to ES */ -function prepESFilter(query, actResource) { +function prepESFilter (query, actResource) { const pageSize = query.perPage || config.get('PAGE_SIZE') const page = query.page || 1 const { sortBy, orderBy } = query @@ -220,7 +220,7 @@ function prepESFilter(query, actResource) { * @param {String} resource Resource name in ES * @return {Object} Data fetched from ES based on the filters */ -function* fetchFromES(query, resource) { +function * fetchFromES (query, resource) { const esClient = getEsClient() // Construct ES filter const filter = prepESFilter(query, resource) @@ -244,7 +244,7 @@ function* fetchFromES(query, resource) { * @param res HTTP response * @param {Object} data Data for which pagination need to be applied */ -function setPaginationHeaders(req, res, data) { +function setPaginationHeaders (req, res, data) { const totalPages = Math.ceil(data.total / data.pageSize) let fullUrl = req.protocol + '://' + req.get('host') + req.url.replace(`&page=${data.page}`, '') // URL formatting to add pagination parameters accordingly @@ -295,7 +295,7 @@ function setPaginationHeaders(req, res, data) { /* Function to get M2M token * @returns {Promise} */ -function* getM2Mtoken() { +function * getM2Mtoken () { return yield m2m.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET) } @@ -304,7 +304,7 @@ function* getM2Mtoken() { * @param {String} challengeId Challenge ID * @returns {String} Legacy Challenge ID of the given challengeId */ -function* getLegacyChallengeId(challengeId) { +function * getLegacyChallengeId (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 uuid. Fetching legacy challenge id`) const token = yield getM2Mtoken() @@ -328,7 +328,7 @@ function* getLegacyChallengeId(challengeId) { * @param {Integer} challengeId Challenge ID * @returns {String} v5 uuid Challenge ID of the given challengeId */ -function* getV5ChallengeId(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() @@ -336,7 +336,7 @@ function* getV5ChallengeId(challengeId) { 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.id') + const v5Uuid = _.get(response, 'body[0].id') logger.debug(`V5 challenge id is ${v5Uuid} for legacy challenge id ${challengeId}`) return v5Uuid } catch (err) { @@ -352,7 +352,7 @@ function* getV5ChallengeId(challengeId) { * @param challengeId Challenge ID * @returns {Integer} Submission phase ID of the given challengeId */ -function* getSubmissionPhaseId(challengeId) { +function * getSubmissionPhaseId (challengeId) { let phaseId = null let response challengeId = getV5ChallengeId(challengeId) @@ -391,7 +391,7 @@ function* getSubmissionPhaseId(challengeId) { * @param subEntity Submission Entity * @returns {Promise} */ -function* checkCreateAccess(authUser, subEntity) { +function * checkCreateAccess (authUser, subEntity) { let challengeDetails let resources @@ -478,7 +478,7 @@ function* checkCreateAccess(authUser, subEntity) { * @param submission Submission Entity * @returns {Promise} */ -function* checkGetAccess(authUser, submission) { +function * checkGetAccess (authUser, submission) { let resources let challengeDetails // Allow downloading Own submission @@ -595,7 +595,7 @@ function* checkGetAccess(authUser, submission) { * @param submission Submission Entity * @returns {Promise} */ -function* checkReviewGetAccess(authUser, submission) { +function * checkReviewGetAccess (authUser, submission) { let challengeDetails const token = yield getM2Mtoken() const challengeId = getV5ChallengeId(submission.challengeId) @@ -635,7 +635,7 @@ function* checkReviewGetAccess(authUser, submission) { * @param{String} fileURL S3 URL of the file to be downloaded * @returns {Buffer} Buffer of downloaded file */ -function* downloadFile(fileURL) { +function * downloadFile (fileURL) { const { bucket, key } = AmazonS3URI(fileURL) logger.info(`downloadFile(): file is on S3 ${bucket} / ${key}`) const downloadedFile = yield s3.getObject({ Bucket: bucket, Key: key }).promise() @@ -648,7 +648,7 @@ function* downloadFile(fileURL) { * Also stores the original topic in the payload * @param {Object} payload Data that needs to be posted to the bus api */ -function* postToBusApi(payload) { +function * postToBusApi (payload) { const busApiClient = getBusApiClient() const originalTopic = payload.topic @@ -668,7 +668,7 @@ function* postToBusApi(payload) { * @param {Array} reviews The reviews to remove metadata from * @param {Object} authUser The authenticated user details */ -function cleanseReviews(reviews, authUser) { +function cleanseReviews (reviews, authUser) { // Not a machine user if (!authUser.scopes) { const admin = _.filter(authUser.roles, role => role.toLowerCase() === 'Administrator'.toLowerCase()) @@ -701,7 +701,7 @@ function cleanseReviews(reviews, authUser) { * Function to get role id to role name map * @returns {Object|null} map */ -function* getRoleIdToRoleNameMap() { +function * getRoleIdToRoleNameMap () { let resourceRoles let resourceRolesMap = null const token = yield getM2Mtoken() @@ -729,7 +729,7 @@ function* getRoleIdToRoleNameMap() { * @param {Object} challengeDetails the challenge details * @returns {('Scheduled' | 'Open' | 'Closed' | 'Invalid')} status of the phase */ -function getPhaseStatus(phaseName, challengeDetails) { +function getPhaseStatus (phaseName, challengeDetails) { const { phases } = challengeDetails const queriedPhaseIndex = _.findIndex(phases, phase => { return phase.name === phaseName @@ -772,5 +772,6 @@ module.exports = { downloadFile, postToBusApi, cleanseReviews, - getRoleIdToRoleNameMap + getRoleIdToRoleNameMap, + getV5ChallengeId } From 219acd25db373355bb07942bcfb2259e54c38c8f Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Wed, 10 Feb 2021 19:11:09 +0530 Subject: [PATCH 19/31] #210 - Swap challenge id details when responding to api request or posting to kafka --- src/common/helper.js | 22 +++++++++++++++----- src/services/SubmissionService.js | 34 +++++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/common/helper.js b/src/common/helper.js index 9e769bfc..bce61c86 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -355,7 +355,7 @@ function * getV5ChallengeId (challengeId) { function * getSubmissionPhaseId (challengeId) { let phaseId = null let response - challengeId = getV5ChallengeId(challengeId) + challengeId = yield getV5ChallengeId(challengeId) try { logger.info(`Calling to challenge API to find submission phase Id for ${challengeId}`) @@ -395,7 +395,7 @@ function * checkCreateAccess (authUser, subEntity) { let challengeDetails let resources - const challengeId = getV5ChallengeId(subEntity.challengeId) + const challengeId = yield getV5ChallengeId(subEntity.challengeId) // User can only create submission for themselves if (authUser.userId !== subEntity.memberId) { @@ -487,7 +487,7 @@ function * checkGetAccess (authUser, submission) { } const token = yield getM2Mtoken() - const challengeId = getV5ChallengeId(submission.challengeId) + const challengeId = yield getV5ChallengeId(submission.challengeId) try { resources = yield request.get(`${config.RESOURCEAPI_V5_BASE_URL}/resources?challengeId=${challengeId}`) @@ -598,7 +598,7 @@ function * checkGetAccess (authUser, submission) { function * checkReviewGetAccess (authUser, submission) { let challengeDetails const token = yield getM2Mtoken() - const challengeId = getV5ChallengeId(submission.challengeId) + const challengeId = yield getV5ChallengeId(submission.challengeId) try { challengeDetails = yield request.get(`${config.CHALLENGEAPI_V5_URL}/${challengeId}`) @@ -757,6 +757,17 @@ function getPhaseStatus (phaseName, challengeDetails) { } } +/** + * 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 + } +} + module.exports = { wrapExpress, autoWrapExpress, @@ -773,5 +784,6 @@ module.exports = { postToBusApi, cleanseReviews, getRoleIdToRoleNameMap, - getV5ChallengeId + getV5ChallengeId, + adjustSubmissionChallengeId } diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index c96c493f..d8b2ad93 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 } @@ -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,7 +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 = { @@ -278,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, @@ -328,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 = { @@ -365,7 +372,7 @@ createSubmission.schema = { fileType: joi.string(), url: joi.string().uri().trim(), memberId: joi.alternatives().try(joi.id(), joi.string().uuid()).required(), - challengeId: joi.string().uuid().required(), + 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(), @@ -391,6 +398,12 @@ function * _updateSubmission (authUser, submissionId, entity) { } const currDate = (new Date()).toISOString() + let challengeId = exist.challengeId + let legacyChallengeId = exist.legacyChallengeId + if (entity.challengeId) { + challengeId = yield helper.getV5ChallengeId(entity.challengeId) + legacyChallengeId = yield helper.getLegacyChallengeId(entity.challengeId) + } // Record used for updating in Database const record = { TableName: table, @@ -398,12 +411,13 @@ function * _updateSubmission (authUser, submissionId, entity) { id: submissionId }, UpdateExpression: `set #type = :t, #url = :u, memberId = :m, challengeId = :c, - updated = :ua, updatedBy = :ub, submittedDate = :sb`, + 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, ':sb': entity.submittedDate || exist.submittedDate || exist.created @@ -437,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 = { @@ -448,6 +463,7 @@ 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, @@ -466,7 +482,9 @@ function * _updateSubmission (authUser, submissionId, entity) { { updated: currDate, updatedBy: authUser.handle || authUser.sub, - submittedDate: updatedSub.submittedDate + submittedDate: updatedSub.submittedDate, + challengeId: updatedSub.challengeId, + v5ChallengeId: updatedSub.v5ChallengeId } ) } @@ -489,7 +507,7 @@ updateSubmission.schema = { type: joi.string(), url: joi.string().uri().trim().required(), memberId: joi.alternatives().try(joi.id(), joi.string().uuid()).required(), - challengeId: joi.string().uuid().required(), + 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(), @@ -515,7 +533,7 @@ patchSubmission.schema = { type: joi.string(), url: joi.string().uri().trim(), memberId: joi.alternatives().try(joi.id(), joi.string().uuid()), - challengeId: joi.string().uuid(), + 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(), From cc57dc75c82406a857cefebbe8b554a235bef1fa Mon Sep 17 00:00:00 2001 From: sachin-maheshwari Date: Wed, 10 Feb 2021 19:47:05 +0530 Subject: [PATCH 20/31] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e8eb4a18..d71b5cf1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,7 +67,7 @@ workflows: context : org-global filters: branches: - only: ['develop', 'feature/shapeup-pure-v5-task'] + only: ['develop'] - "build-prod": context : org-global filters: From 7aa8f3da7e0023da732376575f90d62a72c280a1 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Thu, 11 Feb 2021 11:18:35 +0530 Subject: [PATCH 21/31] Set schema for challenge id --- scripts/createIndex.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/createIndex.js b/scripts/createIndex.js index 0780a041..eb39d293 100644 --- a/scripts/createIndex.js +++ b/scripts/createIndex.js @@ -18,6 +18,7 @@ co(function * createIndex () { properties: { resource: { type: 'keyword' }, challengeId: { type: 'keyword' }, + legacyChallengeId: { type: 'keyword' }, memberId: { type: 'keyword' }, type: { type: 'keyword' }, isFileSubmission: { type: 'boolean' }, From 6be540ce9a591121e46e76bc16e633cba3da2fe8 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Thu, 11 Feb 2021 14:28:57 +0530 Subject: [PATCH 22/31] Fix issue where after v5 migration, search query would not return any results --- src/services/SubmissionService.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/SubmissionService.js b/src/services/SubmissionService.js index d8b2ad93..35dc0122 100755 --- a/src/services/SubmissionService.js +++ b/src/services/SubmissionService.js @@ -183,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)) From a7c441f2c5bbd51d4913563d8513c06be5e293f6 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Thu, 11 Feb 2021 14:56:56 +0530 Subject: [PATCH 23/31] Output ES query for debug --- src/common/helper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/helper.js b/src/common/helper.js index bce61c86..67fad8fa 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -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) From 2374b05cbc10e44694c969bfe3f0795f8155d601 Mon Sep 17 00:00:00 2001 From: Sachin Maheshwari Date: Thu, 11 Feb 2021 17:03:48 +0530 Subject: [PATCH 24/31] fixing logging issue --- src/common/logger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }) From e4db230b95aaadc29a9bd69fcbcfa01d471cbcd7 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Fri, 12 Feb 2021 13:37:49 +0530 Subject: [PATCH 25/31] #214 - Fix issue where db to es migration script did not migrate all the data --- scripts/migrateFromDBToES.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/scripts/migrateFromDBToES.js b/scripts/migrateFromDBToES.js index 283defe6..e4c9cee8 100644 --- a/scripts/migrateFromDBToES.js +++ b/scripts/migrateFromDBToES.js @@ -25,15 +25,15 @@ 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++) { + logger.debug(`Number of ${tableName}s currently fetched from DB - ` + records.Items.length) + let i = 0 + for (const item of records.Items) { const record = { index: config.get('esConfig.ES_INDEX'), type: config.get('esConfig.ES_TYPE'), - id: records.Items[i].id, + id: item.id, body: { - doc: _.extend({ resource: helper.camelize(tableName) }, records.Items[i]), + doc: _.extend({ resource: helper.camelize(tableName) }, item), doc_as_upsert: true } } @@ -45,12 +45,16 @@ function * migrateRecords (tableName) { promises = [] batchCounter++ } + i++ } // Continue fetching the remaining records from Database if (typeof records.LastEvaluatedKey !== 'undefined') { params.ExclusiveStartKey = records.LastEvaluatedKey } else { + if (promises.length > 0) { + yield promises + } break // If there are no more records to process, exit the loop } } From 82ee6ee2ccd42bb8b6ddade1017090e6c6a8b0b0 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Fri, 12 Feb 2021 15:26:42 +0530 Subject: [PATCH 26/31] #214 - use bulk api for migrating db to es --- scripts/createIndex.js | 2 +- scripts/migrateFromDBToES.js | 35 +++++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/scripts/createIndex.js b/scripts/createIndex.js index eb39d293..2d1f1208 100644 --- a/scripts/createIndex.js +++ b/scripts/createIndex.js @@ -33,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/migrateFromDBToES.js b/scripts/migrateFromDBToES.js index e4c9cee8..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 @@ -28,21 +28,23 @@ function * migrateRecords (tableName) { logger.debug(`Number of ${tableName}s currently fetched from DB - ` + records.Items.length) let i = 0 for (const item of records.Items) { - const record = { - index: config.get('esConfig.ES_INDEX'), - type: config.get('esConfig.ES_TYPE'), - id: item.id, - body: { - doc: _.extend({ resource: helper.camelize(tableName) }, item), - doc_as_upsert: true + // 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++ @@ -52,8 +54,13 @@ function * migrateRecords (tableName) { if (typeof records.LastEvaluatedKey !== 'undefined') { params.ExclusiveStartKey = records.LastEvaluatedKey } else { - if (promises.length > 0) { - yield promises + 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 } From 792ef76d294100b21cbe9bfaef0f64cb223706fa Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Thu, 25 Feb 2021 17:57:35 +0530 Subject: [PATCH 27/31] Debug update challenge id script --- scripts/updateToV5ChallengeId.js | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/scripts/updateToV5ChallengeId.js b/scripts/updateToV5ChallengeId.js index b12ace12..4bb30928 100644 --- a/scripts/updateToV5ChallengeId.js +++ b/scripts/updateToV5ChallengeId.js @@ -12,10 +12,18 @@ 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 * @returns {Promise} */ -function * updateRecord (submission) { - const v5challengeId = yield helper.getV5ChallengeId(submission.challengeId) +function * updateRecord (submission, failedContainer) { + let v5challengeId + try { + v5challengeId = yield helper.getV5ChallengeId(submission.challengeId) + } catch (err) { + logger.error(`fetching the details of the challenge(${submission.challengeId}) failed, ${err.message}`) + failedContainer.push(submission) + return + } const record = { TableName: 'Submission', Key: { @@ -29,7 +37,7 @@ function * updateRecord (submission) { } if (!v5challengeId) { logger.warn(`the challengeId: ${submission.challengeId} is not having a v5 challengeId`) - + failedContainer.push(submission) return } else if (v5challengeId === submission.challengeId) { logger.info(`the challengeId: ${submission.challengeId} is already a v5 challengeId`) @@ -44,18 +52,21 @@ function * updateRecord (submission) { */ function * updateRecords () { const tableName = config.SUBMISSION_TABLE_NAME - let promises = [] + const promises = [] + const failedRecords = [] const params = { TableName: tableName } // Process until all the records from DB is fetched - while (true) { + let i = 20 + while (i > 10) { + i-- 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)) + promises.push(updateRecord(record, failedRecords)) } // Continue fetching the remaining records from Database if (typeof records.LastEvaluatedKey !== 'undefined') { @@ -69,6 +80,11 @@ function * updateRecords () { 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 * () { From 2fd86b5899c017272a2a4ab02ba3c53e0be4f488 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Thu, 25 Feb 2021 17:58:42 +0530 Subject: [PATCH 28/31] remove debug statements --- scripts/updateToV5ChallengeId.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/updateToV5ChallengeId.js b/scripts/updateToV5ChallengeId.js index 4bb30928..10169a87 100644 --- a/scripts/updateToV5ChallengeId.js +++ b/scripts/updateToV5ChallengeId.js @@ -58,9 +58,7 @@ function * updateRecords () { TableName: tableName } // Process until all the records from DB is fetched - let i = 20 - while (i > 10) { - i-- + 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)`) From e372de7469329a0907ba34b774492af59cebd931 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Thu, 25 Feb 2021 19:10:30 +0530 Subject: [PATCH 29/31] Do not update record unnecessarily --- scripts/updateToV5ChallengeId.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/updateToV5ChallengeId.js b/scripts/updateToV5ChallengeId.js index 10169a87..326e824d 100644 --- a/scripts/updateToV5ChallengeId.js +++ b/scripts/updateToV5ChallengeId.js @@ -38,12 +38,11 @@ function * updateRecord (submission, failedContainer) { if (!v5challengeId) { logger.warn(`the challengeId: ${submission.challengeId} is not having a v5 challengeId`) failedContainer.push(submission) - return } else if (v5challengeId === submission.challengeId) { logger.info(`the challengeId: ${submission.challengeId} is already a v5 challengeId`) + } else { + yield dbhelper.updateRecord(record) } - - yield dbhelper.updateRecord(record) } /* From 59d78b1ef0777596a106708ed5f97327345d1748 Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Sun, 28 Feb 2021 11:25:03 +0530 Subject: [PATCH 30/31] #221 - Script to update current challenge id to v5 challenge id in submission record now only works on latest challenges --- README.md | 2 ++ config/default.js | 6 ++-- scripts/updateToV5ChallengeId.js | 62 ++++++++++++++++---------------- src/common/helper.js | 29 ++++++++++++++- 4 files changed, 66 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 75723530..e397e1b2 100755 --- a/README.md +++ b/README.md @@ -180,6 +180,8 @@ To update the existing challengeId data on submissions in DynamoDB to v5 challen ```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 ``` diff --git a/config/default.js b/config/default.js index cfef076f..fa8090f6 100755 --- a/config/default.js +++ b/config/default.js @@ -37,7 +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, - UPDATE_V5_CHALLENGE_BATCH_SIZE: process.env.UPDATE_V5_CHALLENGE_BATCH_SIZE || 250, + 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 + 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/scripts/updateToV5ChallengeId.js b/scripts/updateToV5ChallengeId.js index 326e824d..617f17f4 100644 --- a/scripts/updateToV5ChallengeId.js +++ b/scripts/updateToV5ChallengeId.js @@ -13,17 +13,10 @@ 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) { - let v5challengeId - try { - v5challengeId = yield helper.getV5ChallengeId(submission.challengeId) - } catch (err) { - logger.error(`fetching the details of the challenge(${submission.challengeId}) failed, ${err.message}`) - failedContainer.push(submission) - return - } +function * updateRecord (submission, failedContainer, v5challengeId) { const record = { TableName: 'Submission', Key: { @@ -35,13 +28,11 @@ function * updateRecord (submission, failedContainer) { ':l': submission.challengeId } } - if (!v5challengeId) { - logger.warn(`the challengeId: ${submission.challengeId} is not having a v5 challengeId`) - failedContainer.push(submission) - } else if (v5challengeId === submission.challengeId) { - logger.info(`the challengeId: ${submission.challengeId} is already a v5 challengeId`) - } else { + try { yield dbhelper.updateRecord(record) + } catch (err) { + logger.error(`update submission record error: ${err.message}`) + failedContainer.push(submission) } } @@ -53,23 +44,34 @@ function * updateRecords () { const tableName = config.SUBMISSION_TABLE_NAME const promises = [] const failedRecords = [] - const params = { - TableName: 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}. 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)) + 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 } - // 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 + 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}`) diff --git a/src/common/helper.js b/src/common/helper.js index 67fad8fa..f3170bf9 100755 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -769,6 +769,32 @@ function adjustSubmissionChallengeId (submission) { } } +/** + * 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, @@ -786,5 +812,6 @@ module.exports = { cleanseReviews, getRoleIdToRoleNameMap, getV5ChallengeId, - adjustSubmissionChallengeId + adjustSubmissionChallengeId, + getLatestChallenges } From b0328d9027159efc41e0690f25035f58a1293cae Mon Sep 17 00:00:00 2001 From: Mithun Kamath Date: Sun, 28 Feb 2021 14:57:12 +0530 Subject: [PATCH 31/31] #223 - New script to update legacy challenge id, with filter in code instead of in db call --- package.json | 1 + scripts/updateToV5ChallengeIdV2.js | 87 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 scripts/updateToV5ChallengeIdV2.js diff --git a/package.json b/package.json index 14ec3b52..9a75b5da 100755 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "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/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) +})