From d9e792a9601c4f0e355ff025bdc8c4ad683c57c5 Mon Sep 17 00:00:00 2001 From: Maneth Date: Sat, 14 Dec 2019 19:33:36 -0500 Subject: [PATCH 01/19] Batch Accept done --- middlewares/hacker.middleware.js | 49 ++++++++++++++++++++++++++++++++ routes/api/hacker.js | 33 ++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index affede09..88692309 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -523,6 +523,42 @@ async function updateHacker(req, res, next) { } } +/** + * Updates a hacker that is specified by req.params.id, and then sets req.email + * to the email of the hacker, found in Account. + * @param {{params:{id: String[]}, body: *}} req + * @param {*} res + * @param {*} next + */ +async function updateBatchHacker(req, res, next) { + req.params.id.forEach(async (id) => { + const hacker = await Services.Hacker.updateOne(id , req.body); + if (hacker) { + const acct = await Services.Account.findById(hacker.accountId); + if (!acct) { + return next({ + status: 500, + message: Constants.Error.HACKER_UPDATE_500_MESSAGE, + data: { + hackerId: hacker.id, + accountId: hacker.accountId + } + }); + } + req.email = acct.email; + return next(); + } else { + return next({ + status: 404, + message: Constants.Error.HACKER_404_MESSAGE, + data: { + id: req.params.id + } + }); + } + }); +} + /** * Sets req.body.status to Accepted for next middleware. * @param {{params:{id: string}, body: *}} req @@ -534,6 +570,17 @@ function parseAccept(req, res, next) { next(); } +/** + * Sets req.body.status to Accepted for next middleware. + * @param {{params:{id: string}, body: *}} req + * @param {*} res + * @param {*} next + */ +function parseBatchAccept(req, res, next) { + req.body.status = Constants.General.HACKER_STATUS_ACCEPTED; + next(); +} + /** * @function createhacker @@ -652,7 +699,9 @@ module.exports = { sendAppliedStatusEmail ), updateHacker: Middleware.Util.asyncMiddleware(updateHacker), + updateBatchHacker: Middleware.Util.asyncMiddleware(updateBatchHacker), parseAccept: parseAccept, + parseBatch: parseBatchAccept, validateConfirmedStatusFromAccountId: Middleware.Util.asyncMiddleware( validateConfirmedStatusFromAccountId ), diff --git a/routes/api/hacker.js b/routes/api/hacker.js index c84bbe2a..661a9531 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -283,7 +283,7 @@ module.exports = { * @apiName acceptHacker * @apiGroup Hacker * @apiVersion 2.0.0 - * + * * @apiSuccess {string} message Success message * @apiSuccess {object} data Hacker object * @apiSuccessExample {object} Success-Response: @@ -308,6 +308,37 @@ module.exports = { Controllers.Hacker.updatedHacker ); + /** + * @api {patch} /hacker/accept/:id accept a Hacker + * @apiName acceptHacker + * @apiGroup Hacker + * @apiVersion 2.0.0 + * + * @apiParam (body) {ObjectId[]} Array of id(s) that needed to be accepted + * + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Changed hacker information", + * "data": { + * "status": "Accepted" + * } + * } + * @apiPermission Administrator + */ + hackerRouter + .route("/batchAccept") + .patch( + Middleware.Validator.RouteParam.idValidator, + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), + Middleware.Hacker.validateConfirmedStatusFromHackerId, + Middleware.Hacker.parseAccept, + Middleware.Hacker.updateBatchHacker, + Middleware.Hacker.sendStatusUpdateEmail, + Controllers.Hacker.updatedHacker + ); /** * @api {patch} /hacker/checkin/:id update a hacker's status to be 'Checked-in'. Note that the Hacker must eitehr be Accepted or Confirmed. * @apiName checkinHacker From 76c0b7cf65c190f60458231f9cd170e0a4c0e87c Mon Sep 17 00:00:00 2001 From: Maneth Date: Sun, 15 Dec 2019 21:02:21 -0500 Subject: [PATCH 02/19] Route created --- routes/api/hacker.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/routes/api/hacker.js b/routes/api/hacker.js index 661a9531..ecf7039b 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -309,7 +309,7 @@ module.exports = { ); /** - * @api {patch} /hacker/accept/:id accept a Hacker + * @api {patch} /hacker/batchAccept/ accept array of Hackers * @apiName acceptHacker * @apiGroup Hacker * @apiVersion 2.0.0 @@ -330,10 +330,9 @@ module.exports = { hackerRouter .route("/batchAccept") .patch( - Middleware.Validator.RouteParam.idValidator, Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findById]), - Middleware.Hacker.validateConfirmedStatusFromHackerId, + Middleware.Auth.ensureAuthorized([Services.Hacker.findIds]), + Middleware.Hacker.validateConfirmedStatusFromArrayofHackerIds, Middleware.Hacker.parseAccept, Middleware.Hacker.updateBatchHacker, Middleware.Hacker.sendStatusUpdateEmail, From e5d09fa17bd627e87245646f929eb9c0aa1b065c Mon Sep 17 00:00:00 2001 From: Maneth Date: Sun, 15 Dec 2019 21:02:36 -0500 Subject: [PATCH 03/19] Routes constant edited --- constants/routes.constant.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/constants/routes.constant.js b/constants/routes.constant.js index e22e1d28..dbb8b6cb 100644 --- a/constants/routes.constant.js +++ b/constants/routes.constant.js @@ -128,6 +128,10 @@ const hackerRoutes = { requestType: Constants.REQUEST_TYPES.PATCH, uri: "/api/hacker/accept/" + Constants.ROLE_CATEGORIES.ALL, }, + patchAcceptHackerByArrayOfIds: { + requestType: Constants.REQUEST_TYPES.PATCH, + uri: "/api/hacker/batchAccept" + Constants.ROLE_CATEGORIES.ALL, + }, postAnySendWeekOfEmail: { requestType: Constants.REQUEST_TYPES.POST, uri: "/api/hacker/email/weekOf/" + Constants.ROLE_CATEGORIES.ALL From 149481486176495c75b73cd5a6128314853ef188 Mon Sep 17 00:00:00 2001 From: Maneth Date: Sun, 15 Dec 2019 21:02:52 -0500 Subject: [PATCH 04/19] Middleware added --- middlewares/hacker.middleware.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index 88692309..e0231007 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -167,6 +167,27 @@ async function validateConfirmedStatusFromHackerId(req, res, next) { return validateConfirmedStatus(account, next); } +/** + * Verifies that account is confirmed and of proper type from the hacker ID passed in req.params.id + * @param {{params: {id: ObjectId}}} req + * @param {*} res + * @param {(err?) => void} next + */ +async function validateConfirmedStatusFromArrayofHackerIds(req, res, next) { + req.body.ids.forEach(async (id) => { + const hacker = await Services.Hacker.findById(id); + if (hacker == null) { + return next({ + status: 404, + message: Constants.Error.HACKER_404_MESSAGE, + data: req.body.hackerId + }); + } + const account = await Services.Account.findById(hacker.accountId); + return validateConfirmedStatus(account, next); + }); +} + /** * Verifies that account is confirmed and of proper type from the account object passed in req.body.account * @param {{body: {account: Object}}} req @@ -531,7 +552,7 @@ async function updateHacker(req, res, next) { * @param {*} next */ async function updateBatchHacker(req, res, next) { - req.params.id.forEach(async (id) => { + req.body.ids.forEach(async (id) => { const hacker = await Services.Hacker.updateOne(id , req.body); if (hacker) { const acct = await Services.Account.findById(hacker.accountId); @@ -708,6 +729,9 @@ module.exports = { validateConfirmedStatusFromHackerId: Middleware.Util.asyncMiddleware( validateConfirmedStatusFromHackerId ), + validateConfirmedStatusFromArrayofHackerIds: Middleware.Util.asyncMiddleware( + validateConfirmedStatusFromArrayofHackerIds + ), validateConfirmedStatusFromObject: Middleware.Util.asyncMiddleware( validateConfirmedStatusFromObject ), From 2722d76c402ff5144ea68b14629fd5d9dabdbf98 Mon Sep 17 00:00:00 2001 From: Maneth Date: Sat, 11 Jan 2020 02:33:20 -0500 Subject: [PATCH 05/19] Sending emails for multiple ids enabled --- middlewares/hacker.middleware.js | 39 ++++++++++++++++++++++++++++++++ routes/api/hacker.js | 4 ++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index e0231007..6e58420c 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -349,6 +349,42 @@ async function sendStatusUpdateEmail(req, res, next) { } } +/** + * Sends a preset email to a user if a status change occured. + * @param {{body: {status?: string}, params: {id: string}}} req + * @param {*} res + * @param {(err?:*)=>void} next + */ +async function sendStatusUpdateEmailForMultipleIds(req, res, next) { + //skip if the status doesn't exist + if (!req.body.status) { + return next(); + } else { + // send it to the hacker that is being updated. + req.body.ids.forEach(async (id) => { + const hacker = await Services.Hacker.findById(id); + const account = await Services.Account.findById(hacker.accountId); + if (!hacker) { + return next({ + status: 404, + message: Constants.Error.HACKER_404_MESSAGE + }); + } else if (!account) { + return next({ + status: 500, + message: Constants.Error.GENERIC_500_MESSAGE + }); + } + Services.Email.sendStatusUpdate( + account.firstName, + account.email, + req.body.status, + next + ); + }) + } +} + /** * Sends an email telling the user that they have applied. This is used exclusively when we POST a hacker. * @param {{body: {hacker: {accountId: string}}}} req @@ -716,6 +752,9 @@ module.exports = { sendStatusUpdateEmail: Middleware.Util.asyncMiddleware( sendStatusUpdateEmail ), + sendStatusUpdateEmailForMultipleIds: Middleware.Util.asyncMiddleware( + sendStatusUpdateEmailForMultipleIds + ), sendAppliedStatusEmail: Middleware.Util.asyncMiddleware( sendAppliedStatusEmail ), diff --git a/routes/api/hacker.js b/routes/api/hacker.js index ecf7039b..098be64a 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -331,11 +331,11 @@ module.exports = { .route("/batchAccept") .patch( Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findIds]), + //Middleware.Auth.ensureAuthorized([Services.Hacker.findIds]), Middleware.Hacker.validateConfirmedStatusFromArrayofHackerIds, Middleware.Hacker.parseAccept, Middleware.Hacker.updateBatchHacker, - Middleware.Hacker.sendStatusUpdateEmail, + Middleware.Hacker.sendStatusUpdateEmailForMultipleIds, Controllers.Hacker.updatedHacker ); /** From 1236ad2dc0c35007a367befc946c558e4b07c56a Mon Sep 17 00:00:00 2001 From: Maneth Date: Sat, 11 Jan 2020 15:02:35 -0500 Subject: [PATCH 06/19] routes.constant.js edited --- constants/routes.constant.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constants/routes.constant.js b/constants/routes.constant.js index dbb8b6cb..87fb9908 100644 --- a/constants/routes.constant.js +++ b/constants/routes.constant.js @@ -130,7 +130,7 @@ const hackerRoutes = { }, patchAcceptHackerByArrayOfIds: { requestType: Constants.REQUEST_TYPES.PATCH, - uri: "/api/hacker/batchAccept" + Constants.ROLE_CATEGORIES.ALL, + uri: "/api/hacker/batchAccept", }, postAnySendWeekOfEmail: { requestType: Constants.REQUEST_TYPES.POST, From 46d0037c434d83a66d1a363e68a4951cacc988b4 Mon Sep 17 00:00:00 2001 From: Maneth Date: Sat, 11 Jan 2020 15:03:10 -0500 Subject: [PATCH 07/19] ensure authorised added --- routes/api/hacker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api/hacker.js b/routes/api/hacker.js index 098be64a..dd3b4b44 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -331,7 +331,7 @@ module.exports = { .route("/batchAccept") .patch( Middleware.Auth.ensureAuthenticated(), - //Middleware.Auth.ensureAuthorized([Services.Hacker.findIds]), + Middleware.Auth.ensureAuthorized(), Middleware.Hacker.validateConfirmedStatusFromArrayofHackerIds, Middleware.Hacker.parseAccept, Middleware.Hacker.updateBatchHacker, From c6c1355bcd594f7132a9d4e3c7eadcb8228da1f8 Mon Sep 17 00:00:00 2001 From: Maneth Date: Sat, 11 Jan 2020 15:03:50 -0500 Subject: [PATCH 08/19] Middleware made to handle errors --- middlewares/hacker.middleware.js | 150 +++++++++++++++++++------------ 1 file changed, 91 insertions(+), 59 deletions(-) diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index 6e58420c..3eb17cb7 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -137,6 +137,22 @@ async function validateConfirmedStatus(account, next) { } } +/** + * Helper function that validates if account is confirmed and is of proper type and returns error + * @param account account object containing the information for an account + */ +function getErrors(account) { + if (!account) { + return Constants.Error.ACCOUNT_404_MESSAGE; + } else if (!account.confirmed) { + return Constants.Error.ACCOUNT_403_MESSAGE; + } else if (account.accountType !== Constants.General.HACKER) { + return Constants.Error.ACCOUNT_TYPE_409_MESSAGE; + } else { + return ""; + } +} + /** * Verifies that account is confirmed and of proper type from the account ID passed in req.body.accountId * @param {{body: {accountId: ObjectId}}} req @@ -158,9 +174,9 @@ async function validateConfirmedStatusFromHackerId(req, res, next) { const hacker = await Services.Hacker.findById(req.params.id); if (hacker == null) { return next({ - status: 404, - message: Constants.Error.HACKER_404_MESSAGE, - data: req.body.hackerId + status: 404, + message: Constants.Error.HACKER_404_MESSAGE, + data: req.body.hackerId }); } const account = await Services.Account.findById(hacker.accountId); @@ -174,17 +190,29 @@ async function validateConfirmedStatusFromHackerId(req, res, next) { * @param {(err?) => void} next */ async function validateConfirmedStatusFromArrayofHackerIds(req, res, next) { - req.body.ids.forEach(async (id) => { - const hacker = await Services.Hacker.findById(id); - if (hacker == null) { - return next({ - status: 404, - message: Constants.Error.HACKER_404_MESSAGE, - data: req.body.hackerId - }); - } - const account = await Services.Account.findById(hacker.accountId); - return validateConfirmedStatus(account, next); + req.body.errors = []; + const promise = new Promise((resolve, reject) => { + req.body.ids.forEach(async (id, index) => { + const hacker = await Services.Hacker.findById(id); + if (hacker == null) { + req.body.errors.push([id, "Hacker Not Found"]); + req.body.ids.splice(index, 1); + if (index == req.body.ids.length-1) resolve(); + return; + } + const account = await Services.Account.findById(hacker.accountId); + const error = getErrors(account, next); + if (error) { + req.body.errors.push([id, error]); + console.log(req.body.errors); + req.body.ids.splice(index, 1); + } + if (index == req.body.ids.length-1) resolve(); + }); + }); + + promise.then(() => { + return next(); }); } @@ -361,26 +389,34 @@ async function sendStatusUpdateEmailForMultipleIds(req, res, next) { return next(); } else { // send it to the hacker that is being updated. - req.body.ids.forEach(async (id) => { - const hacker = await Services.Hacker.findById(id); - const account = await Services.Account.findById(hacker.accountId); - if (!hacker) { - return next({ - status: 404, - message: Constants.Error.HACKER_404_MESSAGE - }); - } else if (!account) { - return next({ - status: 500, - message: Constants.Error.GENERIC_500_MESSAGE - }); - } - Services.Email.sendStatusUpdate( - account.firstName, - account.email, - req.body.status, - next - ); + const promise = new Promise ((resolve, reject) => { + req.body.ids.forEach(async (id, index) => { + const hacker = await Services.Hacker.findById(id); + const account = await Services.Account.findById(hacker.accountId); + if (!hacker) { + req.body.errors.push(id, Constants.Error.HACKER_404_MESSAGE); + req.body.ids.splice(index, 1); + if (index == req.body.ids.length-1) resolve(); + return; + } else if (!account) { + req.body.errors.push(id, Constants.Error.GENERIC_500_MESSAGE); + req.body.ids.splice(index, 1); + if (index == req.body.ids.length-1) resolve(); + return; + } + + Services.Email.sendStatusUpdate( + account.firstName, + account.email, + req.body.status, + next + ); + if (index == req.body.ids.length-1) resolve(); + }) + }) + + promise.then(() => { + return next(); }) } } @@ -588,39 +624,36 @@ async function updateHacker(req, res, next) { * @param {*} next */ async function updateBatchHacker(req, res, next) { - req.body.ids.forEach(async (id) => { - const hacker = await Services.Hacker.updateOne(id , req.body); + const promise = new Promise((resolve, reject) => { + req.body.ids.forEach(async (id, index) => { + const hacker = await Services.Hacker.updateOne(id, req.body); if (hacker) { const acct = await Services.Account.findById(hacker.accountId); if (!acct) { - return next({ - status: 500, - message: Constants.Error.HACKER_UPDATE_500_MESSAGE, - data: { - hackerId: hacker.id, - accountId: hacker.accountId - } - }); + req.body.errors.push([id, Constants.Error.HACKER_UPDATE_500_MESSAGE]); + req.body.ids.splice(index, 1); + if (index == req.body.ids.length-1) resolve(); + return; } req.email = acct.email; - return next(); + if (index == req.body.ids.length-1) resolve(); } else { - return next({ - status: 404, - message: Constants.Error.HACKER_404_MESSAGE, - data: { - id: req.params.id - } - }); + req.body.errors([id, Constants.Error.HACKER_404_MESSAGE]); + req.body.ids.splice(index, 1); + if (index == req.body.ids.length-1) resolve(); } + }); + }) + promise.then(() => { + return next(); }); } /** * Sets req.body.status to Accepted for next middleware. - * @param {{params:{id: string}, body: *}} req - * @param {*} res - * @param {*} next + * @param {{params:{id: string}, body: *}} req + * @param {*} res + * @param {*} next */ function parseAccept(req, res, next) { req.body.status = Constants.General.HACKER_STATUS_ACCEPTED; @@ -629,16 +662,15 @@ function parseAccept(req, res, next) { /** * Sets req.body.status to Accepted for next middleware. - * @param {{params:{id: string}, body: *}} req - * @param {*} res - * @param {*} next + * @param {{params:{id: string}, body: *}} req + * @param {*} res + * @param {*} next */ function parseBatchAccept(req, res, next) { req.body.status = Constants.General.HACKER_STATUS_ACCEPTED; next(); } - /** * @function createhacker * @param {{body: {hackerDetails: object}}} req From 282a300f498a4fd30743119cd71393e7f2624bd8 Mon Sep 17 00:00:00 2001 From: Maneth Date: Sun, 12 Jan 2020 11:09:58 -0500 Subject: [PATCH 09/19] Req.body.ids check --- middlewares/hacker.middleware.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index 3eb17cb7..6e3417ce 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -191,6 +191,12 @@ async function validateConfirmedStatusFromHackerId(req, res, next) { */ async function validateConfirmedStatusFromArrayofHackerIds(req, res, next) { req.body.errors = []; + if (!req.body.ids) { + return next({ + status: 404, + message: Constants.Error.HACKER_404_MESSAGE + }) + } const promise = new Promise((resolve, reject) => { req.body.ids.forEach(async (id, index) => { const hacker = await Services.Hacker.findById(id); From d70c5c6a7ebee5f7a53ad489fb6aa0433a9849a8 Mon Sep 17 00:00:00 2001 From: Maneth Date: Sun, 12 Jan 2020 11:10:16 -0500 Subject: [PATCH 10/19] Tests written --- middlewares/validators/hacker.validator.js | 3 ++ tests/hacker.test.js | 33 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/middlewares/validators/hacker.validator.js b/middlewares/validators/hacker.validator.js index 8bc7c931..9b64d68d 100644 --- a/middlewares/validators/hacker.validator.js +++ b/middlewares/validators/hacker.validator.js @@ -264,5 +264,8 @@ module.exports = { statsValidator: [ VALIDATOR.searchModelValidator("query", "model", false), VALIDATOR.searchValidator("query", "q") + ], + batchAcceptValidator: [ + VALIDATOR.mongoIdArrayValidator("body", "ids", false) ] }; diff --git a/tests/hacker.test.js b/tests/hacker.test.js index 15e8b9fb..75de25e0 100644 --- a/tests/hacker.test.js +++ b/tests/hacker.test.js @@ -49,6 +49,8 @@ const unconfirmedHacker1 = util.hacker.unconfirmedAccountHacker1; const invalidHacker1 = util.hacker.invalidHacker1; +const validHackerArray = [util.account.hackerAccounts.stored.team[0]._id , util.account.hackerAccounts.stored.team[1]._id]; + describe("GET hacker", function() { // fail on authentication it("should FAIL to list a hacker's information on /api/hacker/:id GET due to authentication", function(done) { @@ -620,6 +622,37 @@ describe("POST create hacker", function() { }); }); +describe("PATCH update multiple hackers", function() { + it("should SUCCEED and accept a hackers on /api/hacker/accept/:id as an Admin", function(done) { + util.auth.login(agent, Admin0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .patch(`/api/hacker/batchAccept/`) + .type("application/json") + .send(validHackerArray) + .end(function(err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal( + Constants.Success.HACKER_UPDATE + ); + res.body.should.have.property("data"); + chai.assert.equal( + JSON.stringify(res.body.data), + JSON.stringify({ + status: "Accepted" + }) + ); + done(); + }); + }); + }); +}); + describe("PATCH update one hacker", function() { // fail on authentication it("should FAIL to update a hacker on /api/hacker/:id GET due to authentication", function(done) { From 0ffda41ee660b6f958ab352e457065b4866a8baf Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sat, 25 Jul 2020 22:39:23 -0400 Subject: [PATCH 11/19] Add HACKER_UPDATE_BATCH success message --- constants/success.constant.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/constants/success.constant.js b/constants/success.constant.js index e908f651..13e63959 100644 --- a/constants/success.constant.js +++ b/constants/success.constant.js @@ -21,6 +21,7 @@ const HACKER_GET_BY_ID = "Hacker found by id."; const HACKER_READ = "Hacker retrieval successful."; const HACKER_CREATE = "Hacker creation successful."; const HACKER_UPDATE = "Hacker update successful."; +const HACKER_UPDATE_BATCH = "Hacker batch update successful."; const HACKER_SENT_WEEK_OF = "Hacker week-of email sent."; const HACKER_SENT_DAY_OF = "Hacker day-of email sent."; @@ -76,6 +77,8 @@ module.exports = { HACKER_CREATE: HACKER_CREATE, HACKER_UPDATE: HACKER_UPDATE, + HACKER_UPDATE_BATCH: HACKER_UPDATE_BATCH, + HACKER_SENT_WEEK_OF: HACKER_SENT_WEEK_OF, HACKER_SENT_DAY_OF: HACKER_SENT_DAY_OF, From 97a1f19e1685f5bbec974728af444a1994795223 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 26 Jul 2020 00:44:11 -0400 Subject: [PATCH 12/19] Add promise.allsettled so that we can wait for many promises to complete --- package-lock.json | 258 +++++++++++++++++++++++++++++++++------------- package.json | 3 +- 2 files changed, 186 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a3ab0cc..ec4aeda2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hackerAPI", - "version": "2.4.0", + "version": "2.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1047,6 +1047,17 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" + } + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -2056,7 +2067,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -2282,28 +2292,53 @@ "dev": true }, "es-abstract": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.3.tgz", - "integrity": "sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw==", - "dev": true, + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", - "string.prototype.trimleft": "^2.1.0", - "string.prototype.trimright": "^2.1.0" + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } } }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -3640,8 +3675,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -4583,7 +4617,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -4601,8 +4634,7 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "has-unicode": { "version": "2.0.1", @@ -4958,6 +4990,11 @@ } } }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", @@ -4974,10 +5011,9 @@ "dev": true }, "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" }, "is-ci": { "version": "1.2.1", @@ -5009,10 +5045,9 @@ } }, "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, "is-descriptor": { "version": "0.1.6", @@ -5081,6 +5116,11 @@ "ip-regex": "^2.0.0" } }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==" + }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", @@ -5144,12 +5184,11 @@ "dev": true }, "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", "requires": { - "has": "^1.0.1" + "has-symbols": "^1.0.1" } }, "is-retry-allowed": { @@ -5158,6 +5197,11 @@ "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", "dev": true }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -5169,11 +5213,15 @@ "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" + }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -5211,6 +5259,20 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==" + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5785,9 +5847,9 @@ } }, "mocha": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz", - "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -5802,7 +5864,7 @@ "js-yaml": "3.13.1", "log-symbols": "2.2.0", "minimatch": "3.0.4", - "mkdirp": "0.5.1", + "mkdirp": "0.5.4", "ms": "2.1.1", "node-environment-flags": "1.0.5", "object.assign": "4.1.0", @@ -5810,8 +5872,8 @@ "supports-color": "6.0.0", "which": "1.3.1", "wide-align": "1.1.3", - "yargs": "13.3.0", - "yargs-parser": "13.1.1", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", "yargs-unparser": "1.6.0" }, "dependencies": { @@ -5830,6 +5892,12 @@ "color-convert": "^1.9.0" } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -5870,6 +5938,21 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -5923,9 +6006,9 @@ "dev": true }, "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { "cliui": "^5.0.0", @@ -5937,7 +6020,17 @@ "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -6326,16 +6419,14 @@ } }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -6350,7 +6441,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, "requires": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", @@ -6359,13 +6449,13 @@ } }, "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "object.pick": { @@ -6651,6 +6741,18 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } + }, "protobufjs": { "version": "6.8.8", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", @@ -7597,24 +7699,22 @@ "strip-ansi": "^3.0.0" } }, - "string.prototype.trimleft": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", - "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", - "dev": true, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5" } }, - "string.prototype.trimright": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", - "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", - "dev": true, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5" } }, "string_decoder": { @@ -8511,6 +8611,12 @@ "color-convert": "^1.9.0" } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -8528,12 +8634,6 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -8572,9 +8672,9 @@ "dev": true }, "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { "cliui": "^5.0.0", @@ -8586,7 +8686,17 @@ "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } diff --git a/package.json b/package.json index d6100ee5..c44f0159 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "multer": "^1.4.2", "passport": "^0.4.0", "passport-local": "^1.0.0", + "promise.allsettled": "^1.0.2", "q": "^1.5.1", "qrcode": "^1.4.4", "winston": "^2.4.4" @@ -52,7 +53,7 @@ "eslint": "6.7.2", "eslint-config-prettier": "6.7.0", "eslint-plugin-prettier": "3.1.1", - "mocha": "^6.2.2", + "mocha": "^6.2.3", "nodemon": "^1.19.4", "prettier": "1.19.1" } From 5273a5500e812416079a8c36cb7a9f2bb9cbbef4 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 26 Jul 2020 00:44:49 -0400 Subject: [PATCH 13/19] Add findByHackerId function to Account --- services/account.service.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/services/account.service.js b/services/account.service.js index b11bd79f..8c7a74b3 100644 --- a/services/account.service.js +++ b/services/account.service.js @@ -1,4 +1,5 @@ "use strict"; +const Hacker = require("../models/hacker.model"); const Account = require("../models/account.model"); const logger = require("./logger.service"); const bcrypt = require("bcrypt"); @@ -20,6 +21,30 @@ function findById(id) { ); } +/** + * @function findByHackerId + * @param {ObjectId} id the Hacker's ID + * @returns {Promise} The account of the hacker, minus the password. Returns null if the hacker does not exist, or if the hacker is not associated with an account. + * Get the account by using the hacker's ID. + */ +async function findByHackerId(id) { + const TAG = `[Account Service # findByHackerId]:`; + const query = { + _id: id + }; + const hacker = await Hacker.findById( + query, + logger.queryCallbackFactory(TAG, "account", query) + ).populate({ + path: "accountId", + select: " -password" + }); + if (!hacker || !hacker.accountId) { + return null; + } + return hacker.accountId; +} + /** * @function findByEmail * @param {String} email @@ -123,6 +148,7 @@ function updatePassword(id, newPassword) { module.exports = { findOne: findOne, findById: findById, + findByHackerId: findByHackerId, findByEmail: findByEmail, addOneAccount: addOneAccount, getAccountIfValid: getAccountIfValid, From 978076dfe149da976335767a29da73d93387383e Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 26 Jul 2020 00:50:22 -0400 Subject: [PATCH 14/19] Add completedBatchUpdate controller function, generalize validation function, fix router --- controllers/hacker.controller.js | 11 ++++ middlewares/validators/hacker.validator.js | 2 +- routes/api/hacker.js | 63 +++++++++++----------- 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/controllers/hacker.controller.js b/controllers/hacker.controller.js index bbcdd3bd..6eb6cf19 100644 --- a/controllers/hacker.controller.js +++ b/controllers/hacker.controller.js @@ -92,8 +92,19 @@ function sentDayOfEmail(req, res) { }); } +function completedBatchUpdate(req, res) { + return res.status(200).json({ + message: Constants.Success.HACKER_UPDATE_BATCH, + data: { + success_ids: req.body.ids, + errors: req.errors || [] + } + }); +} + module.exports = { updatedHacker: updatedHacker, + completedBatchUpdate: completedBatchUpdate, createdHacker: createdHacker, uploadedResume: uploadedResume, downloadedResume: downloadedResume, diff --git a/middlewares/validators/hacker.validator.js b/middlewares/validators/hacker.validator.js index 9b64d68d..44004afd 100644 --- a/middlewares/validators/hacker.validator.js +++ b/middlewares/validators/hacker.validator.js @@ -265,7 +265,7 @@ module.exports = { VALIDATOR.searchModelValidator("query", "model", false), VALIDATOR.searchValidator("query", "q") ], - batchAcceptValidator: [ + batchUpdateValidator: [ VALIDATOR.mongoIdArrayValidator("body", "ids", false) ] }; diff --git a/routes/api/hacker.js b/routes/api/hacker.js index bb2b95e7..903b1410 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -283,7 +283,7 @@ module.exports = { * @apiName acceptHacker * @apiGroup Hacker * @apiVersion 2.0.0 - * + * * @apiSuccess {string} message Success message * @apiSuccess {object} data Hacker object * @apiSuccessExample {object} Success-Response: @@ -307,7 +307,7 @@ module.exports = { Middleware.Hacker.sendStatusUpdateEmail, Controllers.Hacker.updatedHacker ); - + /** * @api {patch} /hacker/acceptEmail/:email accept a Hacker by email * @apiName acceptHacker @@ -326,49 +326,52 @@ module.exports = { * @apiPermission Administrator */ hackerRouter - .route("/acceptEmail/:email") - .patch( - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized([Services.Hacker.findByEmail]), - Middleware.Validator.RouteParam.emailValidator, - Middleware.parseBody.middleware, - Middleware.Hacker.findByEmail, - Middleware.Hacker.parseAcceptEmail, - Middleware.Hacker.obtainEmailByHackerId, - Middleware.Hacker.completeStatusUpdateEmail, - Controllers.Hacker.updatedHacker - ); + .route("/acceptEmail/:email") + .patch( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Hacker.findByEmail]), + Middleware.Validator.RouteParam.emailValidator, + Middleware.parseBody.middleware, + Middleware.Hacker.findByEmail, + Middleware.Hacker.parseAcceptEmail, + Middleware.Hacker.obtainEmailByHackerId, + Middleware.Hacker.completeStatusUpdateEmail, + Controllers.Hacker.updatedHacker + ); - /** + /** * @api {patch} /hacker/batchAccept/ accept array of Hackers * @apiName acceptHacker * @apiGroup Hacker * @apiVersion 2.0.0 * - * @apiParam (body) {ObjectId[]} Array of id(s) that needed to be accepted - * + * @apiParam (body) {{ids: ObjectId[]}} Array of id(s) that needed to be accepted + * * @apiSuccess {string} message Success message - * @apiSuccess {object} data Hacker object + * @apiSuccess {object} data success_ids array and errors array. Errors array will contain a detailed error for why the batch update for a given ID did not work * @apiSuccessExample {object} Success-Response: * { - * "message": "Changed hacker information", + * "message": "Hacker batch update successful.", * "data": { - * "status": "Accepted" + * "success_ids": ["id1", "id2"] + * "errors": [{status: 404, message: "ACCOUNT_NOT_FOUND", account: null, hacker_id: "id3"}] * } * } * @apiPermission Administrator */ hackerRouter - .route("/batchAccept") - .patch( - Middleware.Auth.ensureAuthenticated(), - Middleware.Auth.ensureAuthorized(), - Middleware.Hacker.validateConfirmedStatusFromArrayofHackerIds, - Middleware.Hacker.parseAccept, - Middleware.Hacker.updateBatchHacker, - Middleware.Hacker.sendStatusUpdateEmailForMultipleIds, - Controllers.Hacker.updatedHacker - ); + .route("/batchAccept") + .patch( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([]), + Middleware.Validator.Hacker.batchUpdateValidator, + Middleware.parseBody.middleware, + Middleware.Hacker.validateConfirmedStatusFromArrayofHackerIds, + Middleware.Hacker.parseAccept, + Middleware.Hacker.updateBatchHacker, + Middleware.Hacker.sendStatusUpdateEmailForMultipleIds, + Controllers.Hacker.completedBatchUpdate + ); /** * @api {patch} /hacker/checkin/:id update a hacker's status to be 'Checked-in'. Note that the Hacker must eitehr be Accepted or Confirmed. * @apiName checkinHacker From 4c83f60b4620e535ec9dc742aba9ed0ede36f070 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 26 Jul 2020 00:50:54 -0400 Subject: [PATCH 15/19] Fix middleware. Add many tests. --- middlewares/hacker.middleware.js | 293 +++++++++++++++---------------- tests/hacker.test.js | 101 +++++++++-- 2 files changed, 228 insertions(+), 166 deletions(-) diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index cbe16ea1..32f424f9 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -1,7 +1,12 @@ +/* eslint-disable require-atomic-updates */ "use strict"; const TAG = `[ HACKER.MIDDLEWARE.js ]`; const mongoose = require("mongoose"); + +// shim to allow us to use Promise.allSettled +require("promise.allsettled").shim(); + const Services = { Hacker: require("../services/hacker.service"), Storage: require("../services/storage.service"), @@ -111,47 +116,52 @@ function addDefaultStatus(req, res, next) { } /** - * Helper function that validates if account is confirmed and is of proper type + * Helper function that validates if account is confirmed and is of proper type. * @param account account object containing the information for an account - * @param {(err?) => void} next + * @returns {{status: number, message: string, error: Object}| null} returns error message if invalid, or null if valid. */ -async function validateConfirmedStatus(account, next) { +function validateConfirmedStatus(account) { if (!account) { - return next({ + return { status: 404, message: Constants.Error.ACCOUNT_404_MESSAGE, - error: {} - }); + data: { account: account } + }; } else if (!account.confirmed) { - return next({ + return { status: 403, message: Constants.Error.ACCOUNT_403_MESSAGE, - error: {} - }); + data: { account: { id: account.id, confirmed: account.confirmed } } + }; } else if (account.accountType !== Constants.General.HACKER) { - return next({ + return { status: 409, - message: Constants.Error.ACCOUNT_TYPE_409_MESSAGE - }); + message: Constants.Error.ACCOUNT_TYPE_409_MESSAGE, + data: { + account: { id: account.id, accountType: account.accountType } + } + }; } else { - return next(); + return; } } /** - * Helper function that validates if account is confirmed and is of proper type and returns error - * @param account account object containing the information for an account + * Verifies that account is confirmed and of proper type from the hacker ID + * @param {string} id + * @returns {string} the id if it is confirmed. + * @throws ACCOUNT_404_MESSAGE if hacker / and or the account does not exist + * @throws ACCOUNT_403_MESSAGE if account is not confirmed + * @throws ACCOUNT_409_MESSAGE if account is not hacker */ -function getErrors(account) { - if (!account) { - return Constants.Error.ACCOUNT_404_MESSAGE; - } else if (!account.confirmed) { - return Constants.Error.ACCOUNT_403_MESSAGE; - } else if (account.accountType !== Constants.General.HACKER) { - return Constants.Error.ACCOUNT_TYPE_409_MESSAGE; - } else { - return ""; +async function hackerHasConfirmedAccount(id) { + const account = await Services.Account.findByHackerId(id); + const error = validateConfirmedStatus(account); + if (error) { + error.data.hacker_id = id; + throw error; } + return id; } /** @@ -162,7 +172,7 @@ function getErrors(account) { */ async function validateConfirmedStatusFromAccountId(req, res, next) { const account = await Services.Account.findById(req.body.accountId); - return validateConfirmedStatus(account, next); + return next(validateConfirmedStatus(account)); } /** @@ -172,55 +182,42 @@ async function validateConfirmedStatusFromAccountId(req, res, next) { * @param {(err?) => void} next */ async function validateConfirmedStatusFromHackerId(req, res, next) { - const hacker = await Services.Hacker.findById(req.params.id); - if (hacker == null) { - return next({ - status: 404, - message: Constants.Error.HACKER_404_MESSAGE, - data: req.body.hackerId - }); - } - const account = await Services.Account.findById(hacker.accountId); - return validateConfirmedStatus(account, next); + // Throws error if not confirmed. + await hackerHasConfirmedAccount(req.params.id); + next(); } /** - * Verifies that account is confirmed and of proper type from the hacker ID passed in req.params.id - * @param {{params: {id: ObjectId}}} req + * Verifies that account is confirmed and of proper type from the hacker ID passed in req.body.ids. + * It will remove all ids that are not valid. + * @param {{body: {ids: ObjectId[]}}} req * @param {*} res * @param {(err?) => void} next */ async function validateConfirmedStatusFromArrayofHackerIds(req, res, next) { - req.body.errors = []; if (!req.body.ids) { return next({ status: 404, message: Constants.Error.HACKER_404_MESSAGE }); } - const promise = new Promise((resolve, reject) => { - req.body.ids.forEach(async (id, index) => { - const hacker = await Services.Hacker.findById(id); - if (hacker == null) { - req.body.errors.push([id, "Hacker Not Found"]); - req.body.ids.splice(index, 1); - if (index == req.body.ids.length - 1) resolve(); - return; - } - const account = await Services.Account.findById(hacker.accountId); - const error = getErrors(account, next); - if (error) { - req.body.errors.push([id, error]); - console.log(req.body.errors); - req.body.ids.splice(index, 1); - } - if (index == req.body.ids.length - 1) resolve(); - }); - }); - - promise.then(() => { - return next(); - }); + let confirmedStatusPromises = []; + for (const id of req.body.ids) { + confirmedStatusPromises.push(hackerHasConfirmedAccount(id)); + } + const results = await Promise.allSettled(confirmedStatusPromises); + req.body.ids = []; + req.errors = req.errors ? req.errors : []; + // Iterate through results and split by errors and IDs + for (const result of results) { + if (result.status === "rejected") { + req.errors.push(result.reason); + } else { + // hackerHasConfirmedAccount will return ID + req.body.ids.push(result.value); + } + } + next(); } /** @@ -230,7 +227,7 @@ async function validateConfirmedStatusFromArrayofHackerIds(req, res, next) { * @param {(err?) => void} next */ async function validateConfirmedStatusFromObject(req, res, next) { - return validateConfirmedStatus(req.body.account, next); + next(validateConfirmedStatus(req.body.account)); } /** @@ -350,6 +347,36 @@ async function downloadResume(req, res, next) { } return next(); } + +/** + * Sends the status for a given hacker. + * @returns {Promise} Returns a promise, which resolves into the ID, or rejects with the reason. + * @param {string} id + * @param {string} status + */ +async function sendStatusUpdateEmailHelper(id, status) { + // send it to the hacker that is being updated. + const account = await Services.Account.findByHackerId(id); + if (!account) { + throw { + status: 500, + message: Constants.Error.GENERIC_500_MESSAGE, + id: id + }; + } + // Promisify sendStatusUpdate :/ + return new Promise((resolve, reject) => { + Services.Email.sendStatusUpdate( + account.firstName, + account.email, + status, + (err) => { + err ? reject(err) : resolve(id); + } + ); + }); +} + /** * Sends a preset email to a user if a status change occured. * @param {{body: {status?: string}, params: {id: string}}} req @@ -361,32 +388,14 @@ async function sendStatusUpdateEmail(req, res, next) { if (!req.body.status) { return next(); } else { - // send it to the hacker that is being updated. - const hacker = await Services.Hacker.findById(req.params.id); - const account = await Services.Account.findById(hacker.accountId); - if (!hacker) { - return next({ - status: 404, - message: Constants.Error.HACKER_404_MESSAGE - }); - } else if (!account) { - return next({ - status: 500, - message: Constants.Error.GENERIC_500_MESSAGE - }); - } - Services.Email.sendStatusUpdate( - account.firstName, - account.email, - req.body.status, - next - ); + await sendStatusUpdateEmailHelper(req.params.id, req.body.status); + next(); } } /** * Sends a preset email to a user if a status change occured. - * @param {{body: {status?: string}, params: {id: string}}} req + * @param {{body: {status?: string, ids: string[]}} req * @param {*} res * @param {(err?:*)=>void} next */ @@ -395,44 +404,24 @@ async function sendStatusUpdateEmailForMultipleIds(req, res, next) { if (!req.body.status) { return next(); } else { - // send it to the hacker that is being updated. - const promise = new Promise((resolve, reject) => { - req.body.ids.forEach(async (id, index) => { - const hacker = await Services.Hacker.findById(id); - const account = await Services.Account.findById( - hacker.accountId - ); - if (!hacker) { - req.body.errors.push( - id, - Constants.Error.HACKER_404_MESSAGE - ); - req.body.ids.splice(index, 1); - if (index == req.body.ids.length - 1) resolve(); - return; - } else if (!account) { - req.body.errors.push( - id, - Constants.Error.GENERIC_500_MESSAGE - ); - req.body.ids.splice(index, 1); - if (index == req.body.ids.length - 1) resolve(); - return; - } - - Services.Email.sendStatusUpdate( - account.firstName, - account.email, - req.body.status, - next - ); - if (index == req.body.ids.length - 1) resolve(); - }); - }); - - promise.then(() => { - return next(); - }); + const statusUpdatePromises = []; + for (const id of req.body.ids) { + statusUpdatePromises.push( + sendStatusUpdateEmailHelper(id, req.body.status) + ); + } + const results = await Promise.allSettled(statusUpdatePromises); + req.body.ids = []; + req.errors = req.errors ? req.errors : []; + for (const result of results) { + if (result.status === "rejected") { + req.errors.push(result.reason); + } else { + // result.value will be the hacker's ID. + req.body.ids.push(result.value); + } + } + next(); } } /** @@ -603,7 +592,7 @@ function checkStatus(statuses) { return Middleware.Util.asyncMiddleware(async (req, res, next) => { let hacker = await Services.Hacker.findById(req.params.id); - if (!!hacker) { + if (hacker) { const status = hacker.status; // makes sure the hacker's status is in the accepted statuses list if (statuses.indexOf(status) === -1) { @@ -707,39 +696,32 @@ async function obtainEmailByHackerId(req, res, next) { } /** - * Updates a hacker that is specified by req.params.id, and then sets req.email - * to the email of the hacker, found in Account. - * @param {{params:{id: String[]}, body: *}} req + * Updates a list of hacker that is specified by req.body.ids. + * Some hackers may fail, and that will be stored in req.errors. + * Filters req.body.ids to only the successful processes. + * @param {{body:{ids: String[]}}} req * @param {*} res * @param {*} next */ async function updateBatchHacker(req, res, next) { - const promise = new Promise((resolve, reject) => { - req.body.ids.forEach(async (id, index) => { - const hacker = await Services.Hacker.updateOne(id, req.body); - if (hacker) { - const acct = await Services.Account.findById(hacker.accountId); - if (!acct) { - req.body.errors.push([ - id, - Constants.Error.HACKER_UPDATE_500_MESSAGE - ]); - req.body.ids.splice(index, 1); - if (index == req.body.ids.length - 1) resolve(); - return; - } - req.email = acct.email; - if (index == req.body.ids.length - 1) resolve(); - } else { - req.body.errors([id, Constants.Error.HACKER_404_MESSAGE]); - req.body.ids.splice(index, 1); - if (index == req.body.ids.length - 1) resolve(); - } - }); - }); - promise.then(() => { - return next(); - }); + let eachHackerPromise = []; + for (const id of req.body.ids) { + eachHackerPromise.push(Services.Hacker.updateOne(id, req.body)); + } + const updateResults = await Promise.allSettled(eachHackerPromise); + // clear req.body.ids so that we store only the good ones. + req.body.ids = []; + req.errors = req.errors ? req.errors : []; + for (const result of updateResults) { + // Some error happened when trying to update a hacker. + if (result.status === "rejected") { + req.errors.push(result.reason); + } else { + // result.value will be the hacker object. + req.body.ids.push(result.value._id); + } + } + next(); } /** @@ -803,7 +785,7 @@ async function createHacker(req, res, next) { }); } const hacker = await Services.Hacker.createHacker(hackerDetails); - if (!!hacker) { + if (hacker) { req.body.hacker = hacker; return next(); } else { @@ -858,7 +840,7 @@ async function findSelf(req, res, next) { const hacker = await Services.Hacker.findByAccountId(req.user.id); - if (!!hacker) { + if (hacker) { req.body.hacker = hacker; return next(); } else { @@ -929,5 +911,8 @@ module.exports = { findSelf: Middleware.Util.asyncMiddleware(findSelf), getStats: Middleware.Util.asyncMiddleware(getStats), findById: Middleware.Util.asyncMiddleware(findById), - findByEmail: Middleware.Util.asyncMiddleware(findByEmail) + findByEmail: Middleware.Util.asyncMiddleware(findByEmail), + obtainEmailByHackerId: Middleware.Util.asyncMiddleware( + obtainEmailByHackerId + ) }; diff --git a/tests/hacker.test.js b/tests/hacker.test.js index 02e66e9f..89b82f08 100644 --- a/tests/hacker.test.js +++ b/tests/hacker.test.js @@ -4,7 +4,7 @@ const chaiHttp = require("chai-http"); chai.use(chaiHttp); const server = require("../app"); const agent = chai.request.agent(server.app); -const should = chai.should(); +chai.should(); const Hacker = require("../models/hacker.model"); const fs = require("fs"); const path = require("path"); @@ -50,7 +50,16 @@ const unconfirmedHacker1 = util.hacker.unconfirmedAccountHacker1; const invalidHacker1 = util.hacker.invalidHacker1; -const validHackerArray = [util.account.hackerAccounts.stored.team[0]._id , util.account.hackerAccounts.stored.team[1]._id]; +const BatchAcceptHackerArrayValid = [ + util.hacker.TeamHacker0._id, + util.hacker.TeamHacker1._id, + util.hacker.NoTeamHacker0._id +]; + +const BatchAcceptHackerArrayInvalid = [ + invalidHacker1._id, + unconfirmedHacker1._id +]; describe("GET hacker", function() { // fail on authentication @@ -624,7 +633,7 @@ describe("POST create hacker", function() { }); describe("PATCH update multiple hackers", function() { - it("should SUCCEED and accept a hackers on /api/hacker/accept/:id as an Admin", function(done) { + it("should FAIL input validation on /api/hacker/batchAccept as an Admin", function(done) { util.auth.login(agent, Admin0, (error) => { if (error) { agent.close(); @@ -633,25 +642,94 @@ describe("PATCH update multiple hackers", function() { return agent .patch(`/api/hacker/batchAccept/`) .type("application/json") - .send(validHackerArray) + .send() + .end(function(err, res) { + res.should.have.status(422); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal( + Constants.Error.VALIDATION_422_MESSAGE + ); + done(); + }); + }); + }); + it("should FAIL authorization on /api/hacker/batchAccept as a non-Admin", function(done) { + util.auth.login(agent, noTeamHackerAccount0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .patch(`/api/hacker/batchAccept/`) + .type("application/json") + .send() + .end(function(err, res) { + res.should.have.status(403); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal( + Constants.Error.AUTH_403_MESSAGE + ); + done(); + }); + }); + }); + it("should SUCCEED and accept 2 hackers on /api/hacker/batchAccept as an Admin", function(done) { + util.auth.login(agent, Admin0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .patch(`/api/hacker/batchAccept/`) + .type("application/json") + .send({ ids: BatchAcceptHackerArrayValid }) .end(function(err, res) { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); res.body.message.should.equal( - Constants.Success.HACKER_UPDATE + Constants.Success.HACKER_UPDATE_BATCH ); res.body.should.have.property("data"); chai.assert.equal( JSON.stringify(res.body.data), JSON.stringify({ - status: "Accepted" + success_ids: BatchAcceptHackerArrayValid, + errors: [] }) ); done(); }); }); }); + it("should SUCCEED and accept 0 out of 2 hackers on /api/hacker/batchAccept as an Admin", function(done) { + util.auth.login(agent, Admin0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .patch(`/api/hacker/batchAccept/`) + .type("application/json") + .send({ ids: BatchAcceptHackerArrayInvalid }) + .end(function(err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal( + Constants.Success.HACKER_UPDATE_BATCH + ); + res.body.should.have.property("data"); + res.body.data.should.have.property("success_ids"); + chai.assert.equal(res.body.data.success_ids.length, 0); + res.body.data.should.have.property("errors"); + chai.assert.equal(res.body.data.errors.length, 2); + done(); + }); + }); + }); }); describe("PATCH update one hacker", function() { @@ -727,7 +805,7 @@ describe("PATCH update one hacker", function() { res.should.be.json; res.body.should.have.property("message"); res.body.message.should.equal( - Constants.Error.HACKER_404_MESSAGE + Constants.Error.ACCOUNT_404_MESSAGE ); res.body.should.have.property("data"); @@ -811,7 +889,9 @@ describe("PATCH update one hacker", function() { return done(error); } return agent - .patch(`/api/hacker/acceptEmail/${invalidHackerAccount0[0].email}`) + .patch( + `/api/hacker/acceptEmail/${invalidHackerAccount0[0].email}` + ) .type("application/json") .send() .end(function(err, res) { @@ -846,10 +926,7 @@ describe("PATCH update one hacker", function() { Constants.Success.HACKER_UPDATE ); res.body.should.have.property("data"); - chai.assert.equal( - res.body.data.hacker.status, - "Accepted" - ); + chai.assert.equal(res.body.data.hacker.status, "Accepted"); done(); }); }); From 103d4352badf5649b601a9cf17784f6189fb872f Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 26 Jul 2020 01:22:06 -0400 Subject: [PATCH 16/19] Fix bug where requests hang when response is null --- services/email.service.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/services/email.service.js b/services/email.service.js index dcefe34f..246f245a 100644 --- a/services/email.service.js +++ b/services/email.service.js @@ -74,7 +74,10 @@ class EmailService { html: html }; this.send(mailData).then((response) => { - if (response[0].statusCode >= 200 && response[0].statusCode < 300) { + if ( + !response || + (response[0].statusCode >= 200 && response[0].statusCode < 300) + ) { callback(); } else { callback(response[0]); @@ -102,7 +105,10 @@ class EmailService { html: html }; this.send(mailData).then((response) => { - if (response[0].statusCode >= 200 && response[0].statusCode < 300) { + if ( + !response || + (response[0].statusCode >= 200 && response[0].statusCode < 300) + ) { callback(); } else { callback(response[0]); @@ -124,7 +130,10 @@ class EmailService { }) }; this.send(mailData).then((response) => { - if (response[0].statusCode >= 200 && response[0].statusCode < 300) { + if ( + !response || + (response[0].statusCode >= 200 && response[0].statusCode < 300) + ) { callback(); } else { callback(response[0]); From 691e4c858d9593369893a73de10e375506f365d9 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 26 Jul 2020 16:04:39 -0400 Subject: [PATCH 17/19] rename completedBatchUpdate to updatedHackerBatch --- controllers/hacker.controller.js | 4 ++-- routes/api/hacker.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/controllers/hacker.controller.js b/controllers/hacker.controller.js index 6eb6cf19..25b2a635 100644 --- a/controllers/hacker.controller.js +++ b/controllers/hacker.controller.js @@ -92,7 +92,7 @@ function sentDayOfEmail(req, res) { }); } -function completedBatchUpdate(req, res) { +function updatedHackerBatch(req, res) { return res.status(200).json({ message: Constants.Success.HACKER_UPDATE_BATCH, data: { @@ -104,7 +104,7 @@ function completedBatchUpdate(req, res) { module.exports = { updatedHacker: updatedHacker, - completedBatchUpdate: completedBatchUpdate, + updatedHackerBatch: updatedHackerBatch, createdHacker: createdHacker, uploadedResume: uploadedResume, downloadedResume: downloadedResume, diff --git a/routes/api/hacker.js b/routes/api/hacker.js index 903b1410..68b32468 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -370,7 +370,7 @@ module.exports = { Middleware.Hacker.parseAccept, Middleware.Hacker.updateBatchHacker, Middleware.Hacker.sendStatusUpdateEmailForMultipleIds, - Controllers.Hacker.completedBatchUpdate + Controllers.Hacker.updatedHackerBatch ); /** * @api {patch} /hacker/checkin/:id update a hacker's status to be 'Checked-in'. Note that the Hacker must eitehr be Accepted or Confirmed. From cd558b595edf03544375261c4e6a8952bb57418c Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 26 Jul 2020 16:05:28 -0400 Subject: [PATCH 18/19] Set apiVersion number to be 3.0.0 --- routes/api/hacker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api/hacker.js b/routes/api/hacker.js index 68b32468..f16a35ac 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -343,7 +343,7 @@ module.exports = { * @api {patch} /hacker/batchAccept/ accept array of Hackers * @apiName acceptHacker * @apiGroup Hacker - * @apiVersion 2.0.0 + * @apiVersion 3.0.0 * * @apiParam (body) {{ids: ObjectId[]}} Array of id(s) that needed to be accepted * From e14f9b0864143a3817e086d7f3c84b9f168bdff8 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 26 Jul 2020 16:33:30 -0400 Subject: [PATCH 19/19] Create and then use parseAcceptBatch instead of parseAccept --- middlewares/hacker.middleware.js | 14 +++++++++++++- routes/api/hacker.js | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index 32f424f9..1c70829c 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -725,7 +725,7 @@ async function updateBatchHacker(req, res, next) { } /** - * Sets req.body.status to Accepted for next middleware. + * Sets req.body.status to Accepted for next middleware, and store req.params.id as req.hackerId * @param {{params:{id: string}, body: *}} req * @param {*} res * @param {*} next @@ -748,6 +748,17 @@ function parseAcceptEmail(req, res, next) { next(); } +/** + * Sets req.body.status to Accepted for next middleware. + * @param {{body: *}} req + * @param {*} res + * @param {*} next + */ +function parseAcceptBatch(req, res, next) { + req.body.status = Constants.General.HACKER_STATUS_ACCEPTED; + next(); +} + /** * Sets req.body.status to Accepted for next middleware. * @param {{params:{id: string}, body: *}} req @@ -881,6 +892,7 @@ module.exports = { updateHacker: Middleware.Util.asyncMiddleware(updateHacker), updateBatchHacker: Middleware.Util.asyncMiddleware(updateBatchHacker), parseAccept: parseAccept, + parseAcceptBatch: parseAcceptBatch, parseAcceptEmail: parseAcceptEmail, parseBatch: parseBatchAccept, validateConfirmedStatusFromAccountId: Middleware.Util.asyncMiddleware( diff --git a/routes/api/hacker.js b/routes/api/hacker.js index f16a35ac..038ed869 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -367,7 +367,7 @@ module.exports = { Middleware.Validator.Hacker.batchUpdateValidator, Middleware.parseBody.middleware, Middleware.Hacker.validateConfirmedStatusFromArrayofHackerIds, - Middleware.Hacker.parseAccept, + Middleware.Hacker.parseAcceptBatch, Middleware.Hacker.updateBatchHacker, Middleware.Hacker.sendStatusUpdateEmailForMultipleIds, Controllers.Hacker.updatedHackerBatch