From 80e438bb30c72233777c6f5d58344cd90d0d2e51 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Fri, 18 Jan 2019 13:59:41 -0500 Subject: [PATCH 01/14] Create /self route for sponsor --- constants/role.constant.js | 5 ++ constants/routes.constant.js | 4 ++ middlewares/sponsor.middleware.js | 32 +++++++++++ routes/api/sponsor.js | 27 ++++++++++ tests/sponsor.test.js | 89 ++++++++++++++++++++++++++++++- 5 files changed, 156 insertions(+), 1 deletion(-) diff --git a/constants/role.constant.js b/constants/role.constant.js index b33aa36e..752ca1f3 100644 --- a/constants/role.constant.js +++ b/constants/role.constant.js @@ -69,6 +69,7 @@ const sponsorT1Role = { "routes": [ Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, + Constants.Routes.sponsorRoutes.getSelf, ] }; @@ -78,6 +79,7 @@ const sponsorT2Role = { "routes": [ Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, + Constants.Routes.sponsorRoutes.getSelf, ] }; @@ -87,6 +89,7 @@ const sponsorT3Role = { "routes": [ Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, + Constants.Routes.sponsorRoutes.getSelf, ] }; @@ -96,6 +99,7 @@ const sponsorT4Role = { "routes": [ Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, + Constants.Routes.sponsorRoutes.getSelf, ] }; @@ -105,6 +109,7 @@ const sponsorT5Role = { "routes": [ Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, + Constants.Routes.sponsorRoutes.getSelf, ] }; diff --git a/constants/routes.constant.js b/constants/routes.constant.js index fbeb9f6c..3f00db2c 100644 --- a/constants/routes.constant.js +++ b/constants/routes.constant.js @@ -127,6 +127,10 @@ const hackerRoutes = { }; const sponsorRoutes = { + "getSelf": { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/sponsor/self/", + }, "getSelfById": { requestType: Constants.REQUEST_TYPES.GET, uri: "/api/sponsor/" + Constants.ROLE_CATEGORIES.SELF, diff --git a/middlewares/sponsor.middleware.js b/middlewares/sponsor.middleware.js index a6f93dca..0f256eb5 100644 --- a/middlewares/sponsor.middleware.js +++ b/middlewares/sponsor.middleware.js @@ -87,6 +87,37 @@ async function validateConfirmedStatus(req, res, next) { } } +/** + * Finds the sponsor information of the logged in user + * @param {{user: {id: string, accountType: string}}} req + * @param {*} res + * @param {(err?)=>void} next + */ +async function findSelf(req, res, next) { + if (!Constants.General.SPONSOR_TIERS.includes(req.user.accountType)) { + return res.status(409).json({ + message: Constants.Error.ACCOUNT_TYPE_409_MESSAGE, + data: { + id: req.user.id, + } + }); + } + + const sponsor = await Services.Sponsor.findByAccountId(req.user.id); + + if (!!sponsor) { + req.body.sponsor = sponsor; + return next(); + } else { + return res.status(404).json({ + message: Constants.Error.SPONSOR_404_MESSAGE, + data: { + id: req.user.id, + } + }); + } +} + /** * @async * @function findById @@ -157,6 +188,7 @@ async function checkDuplicateAccountLinks(req, res, next) { module.exports = { parsePatch: parsePatch, parseSponsor: parseSponsor, + findSelf: Middleware.Util.asyncMiddleware(findSelf), findById: Middleware.Util.asyncMiddleware(findById), createSponsor: Middleware.Util.asyncMiddleware(createSponsor), checkDuplicateAccountLinks: Middleware.Util.asyncMiddleware(checkDuplicateAccountLinks), diff --git a/routes/api/sponsor.js b/routes/api/sponsor.js index 01a18778..9d14c9d5 100644 --- a/routes/api/sponsor.js +++ b/routes/api/sponsor.js @@ -25,6 +25,33 @@ module.exports = { activate: function (apiRouter) { const sponsorRouter = new express.Router(); + /** + * @api {get} /sponsor/self get information about logged in sponsor + * @apiName self + * @apiGroup Hacker + * @apiVersion 1.4.1 + * + * @apiSuccess {String} message Success message + * @apiSuccess {Object} data Sponsor object + * @apiSuccessExample {object} Success-Response: + * { + "message": "Successfully retrieved sponsor information", + "data": {...} + } + + * @apiError {String} message Error message + * @apiError {Object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Sponsor not found", "data": {}} + */ + sponsorRouter.route("/self").get( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized(), + + Middleware.Sponsor.findSelf, + Controllers.Sponsor.showSponsor + ); + /** * @api {get} /sponsor/:id get a sponsor's information * @apiName getSponsor diff --git a/tests/sponsor.test.js b/tests/sponsor.test.js index dab90b9d..2b230c87 100644 --- a/tests/sponsor.test.js +++ b/tests/sponsor.test.js @@ -27,8 +27,95 @@ const newT2Sponsor0 = util.sponsor.newT2Sponsor0; let duplicateSponsor = util.sponsor.duplicateAccountLinkSponsor1; +describe("GET user's sponsor info", function () { + it("should fail list a sponsor's information due to authentication from /api/sponsor/self GET", function (done) { + chai.request(server.app) + .get(`/api/sponsor/self`) + // does not have password because of to stripped json + .end(function (err, res) { + res.should.have.status(401); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal(Constants.Error.AUTH_401_MESSAGE); + + done(); + }); + }); + + it("should FAIL to list a sponsor's info due to wrong account type on /api/sponsor/self GET", function (done) { + util.auth.login(agent, Admin0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get(`/api/sponsor/self`) + .end(function (err, res) { + if (err) { + return done(err); + } + res.should.have.status(409); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal(Constants.Error.ACCOUNT_TYPE_409_MESSAGE); + res.body.should.have.property("data"); + + done(); + }); + }); + }); + + it("should FAIL to list a sponsor's info due to lack of sponsor on /api/sponsor/self GET", function (done) { + util.auth.login(agent, newT2SponsorAccount0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get(`/api/sponsor/self`) + .end(function (err, res) { + if (err) { + return done(err); + } + res.should.have.status(404); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal(Constants.Error.SPONSOR_404_MESSAGE); + res.body.should.have.property("data"); + + done(); + }); + }); + }); + + it("should SUCCEED to list user's sponsor info /api/sponsor/self GET", function (done) { + util.auth.login(agent, T1SponsorAccount0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get(`/api/sponsor/self`) + .end(function (err, res) { + if (err) { + return done(err); + } + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal(Constants.Success.SPONSOR_READ); + res.body.should.have.property("data"); + res.body.data.should.be.a("object"); + + let sponsor = new Sponsor(T1Sponsor0); + chai.assert.equal(JSON.stringify(res.body.data), JSON.stringify(sponsor.toJSON())); + done(); + }); + }); + }); +}); -describe("GET user sponsor", function () { +describe("GET sponsor by id", function () { it("should fail list a sponsor's information due to authentication from /api/sponsor/:id GET", function (done) { chai.request(server.app) .get(`/api/sponsor/` + T1Sponsor0._id) From b70e48b4de3d611e8b3d49b9614092c36902c096 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Fri, 18 Jan 2019 14:04:58 -0500 Subject: [PATCH 02/14] docs and add a authentication test --- docs/api/api_data.js | 61 +++++++++++++++++++++++++++++++++++++++ docs/api/api_data.json | 61 +++++++++++++++++++++++++++++++++++++++ docs/api/api_project.js | 30 +++++++++---------- docs/api/api_project.json | 30 +++++++++---------- tests/sponsor.test.js | 23 +++++++++++++++ 5 files changed, 175 insertions(+), 30 deletions(-) diff --git a/docs/api/api_data.js b/docs/api/api_data.js index c7806fac..a7a4b40e 100644 --- a/docs/api/api_data.js +++ b/docs/api/api_data.js @@ -1797,6 +1797,67 @@ define({ "url": "https://api.mchacks.ca/api/hacker/resume/:id" }] }, + { + "type": "get", + "url": "/sponsor/self", + "title": "get information about logged in sponsor", + "name": "self", + "group": "Hacker", + "version": "1.4.1", + "success": { + "fields": { + "Success 200": [{ + "group": "Success 200", + "type": "String", + "optional": false, + "field": "message", + "description": "

Success message

" + }, + { + "group": "Success 200", + "type": "Object", + "optional": false, + "field": "data", + "description": "

Sponsor object

" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {...}\n }", + "type": "object" + }] + }, + "error": { + "fields": { + "Error 4xx": [{ + "group": "Error 4xx", + "type": "String", + "optional": false, + "field": "message", + "description": "

Error message

" + }, + { + "group": "Error 4xx", + "type": "Object", + "optional": false, + "field": "data", + "description": "

empty

" + } + ] + }, + "examples": [{ + "title": "Error-Response: ", + "content": "{\"message\": \"Sponsor not found\", \"data\": {}}", + "type": "object" + }] + }, + "filename": "routes/api/sponsor.js", + "groupTitle": "Hacker", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/sponsor/self" + }] + }, { "type": "get", "url": "/hacker/self", diff --git a/docs/api/api_data.json b/docs/api/api_data.json index 51be9836..6a1d94b3 100644 --- a/docs/api/api_data.json +++ b/docs/api/api_data.json @@ -1796,6 +1796,67 @@ "url": "https://api.mchacks.ca/api/hacker/resume/:id" }] }, + { + "type": "get", + "url": "/sponsor/self", + "title": "get information about logged in sponsor", + "name": "self", + "group": "Hacker", + "version": "1.4.1", + "success": { + "fields": { + "Success 200": [{ + "group": "Success 200", + "type": "String", + "optional": false, + "field": "message", + "description": "

Success message

" + }, + { + "group": "Success 200", + "type": "Object", + "optional": false, + "field": "data", + "description": "

Sponsor object

" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {...}\n }", + "type": "object" + }] + }, + "error": { + "fields": { + "Error 4xx": [{ + "group": "Error 4xx", + "type": "String", + "optional": false, + "field": "message", + "description": "

Error message

" + }, + { + "group": "Error 4xx", + "type": "Object", + "optional": false, + "field": "data", + "description": "

empty

" + } + ] + }, + "examples": [{ + "title": "Error-Response: ", + "content": "{\"message\": \"Sponsor not found\", \"data\": {}}", + "type": "object" + }] + }, + "filename": "routes/api/sponsor.js", + "groupTitle": "Hacker", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/sponsor/self" + }] + }, { "type": "get", "url": "/hacker/self", diff --git a/docs/api/api_project.js b/docs/api/api_project.js index 0cd83632..3b66e564 100644 --- a/docs/api/api_project.js +++ b/docs/api/api_project.js @@ -1,16 +1,16 @@ -define({ - "name": "hackerAPI", - "version": "0.0.8", - "description": "Documentation for the API used for mchacks", - "defaultVersion": "0.0.8", - "title": "hackerAPI documentation", - "url": "https://api.mchacks.ca/api", - "sampleUrl": "https://api.mchacks.ca/api", - "apidoc": "0.3.0", - "generator": { - "name": "apidoc", - "time": "2019-01-10T23:24:21.462Z", - "url": "http://apidocjs.com", - "version": "0.17.7" - } +define({ + "name": "hackerAPI", + "version": "0.0.8", + "description": "Documentation for the API used for mchacks", + "defaultVersion": "0.0.8", + "title": "hackerAPI documentation", + "url": "https://api.mchacks.ca/api", + "sampleUrl": "https://api.mchacks.ca/api", + "apidoc": "0.3.0", + "generator": { + "name": "apidoc", + "time": "2019-01-18T19:02:31.941Z", + "url": "http://apidocjs.com", + "version": "0.17.7" + } }); \ No newline at end of file diff --git a/docs/api/api_project.json b/docs/api/api_project.json index 8ad885dc..8c20a67c 100644 --- a/docs/api/api_project.json +++ b/docs/api/api_project.json @@ -1,16 +1,16 @@ -{ - "name": "hackerAPI", - "version": "0.0.8", - "description": "Documentation for the API used for mchacks", - "defaultVersion": "0.0.8", - "title": "hackerAPI documentation", - "url": "https://api.mchacks.ca/api", - "sampleUrl": "https://api.mchacks.ca/api", - "apidoc": "0.3.0", - "generator": { - "name": "apidoc", - "time": "2019-01-10T23:24:21.462Z", - "url": "http://apidocjs.com", - "version": "0.17.7" - } +{ + "name": "hackerAPI", + "version": "0.0.8", + "description": "Documentation for the API used for mchacks", + "defaultVersion": "0.0.8", + "title": "hackerAPI documentation", + "url": "https://api.mchacks.ca/api", + "sampleUrl": "https://api.mchacks.ca/api", + "apidoc": "0.3.0", + "generator": { + "name": "apidoc", + "time": "2019-01-18T19:02:31.941Z", + "url": "http://apidocjs.com", + "version": "0.17.7" + } } \ No newline at end of file diff --git a/tests/sponsor.test.js b/tests/sponsor.test.js index 2b230c87..d87dd584 100644 --- a/tests/sponsor.test.js +++ b/tests/sponsor.test.js @@ -42,6 +42,29 @@ describe("GET user's sponsor info", function () { }); }); + it("should FAIL to list a sponsor's info due to authorization /api/sponsor/self GET", function (done) { + util.auth.login(agent, HackerAccount0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get(`/api/sponsor/self`) + .end(function (err, res) { + if (err) { + return done(err); + } + 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); + res.body.should.have.property("data"); + + done(); + }); + }); + }); + it("should FAIL to list a sponsor's info due to wrong account type on /api/sponsor/self GET", function (done) { util.auth.login(agent, Admin0, (error) => { if (error) { From 83a51c9fcb6381ad61336130b77f7ddaea010147 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 19 Jan 2019 17:07:20 -0500 Subject: [PATCH 03/14] Fix PR comments --- middlewares/sponsor.middleware.js | 14 ++++++++------ routes/api/sponsor.js | 10 +++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/middlewares/sponsor.middleware.js b/middlewares/sponsor.middleware.js index 0f256eb5..48193523 100644 --- a/middlewares/sponsor.middleware.js +++ b/middlewares/sponsor.middleware.js @@ -95,10 +95,11 @@ async function validateConfirmedStatus(req, res, next) { */ async function findSelf(req, res, next) { if (!Constants.General.SPONSOR_TIERS.includes(req.user.accountType)) { - return res.status(409).json({ + return next({ + status: 409, message: Constants.Error.ACCOUNT_TYPE_409_MESSAGE, - data: { - id: req.user.id, + error: { + id: req.user.id } }); } @@ -109,10 +110,11 @@ async function findSelf(req, res, next) { req.body.sponsor = sponsor; return next(); } else { - return res.status(404).json({ + return next({ + status: 404, message: Constants.Error.SPONSOR_404_MESSAGE, - data: { - id: req.user.id, + error: { + id: req.user.id } }); } diff --git a/routes/api/sponsor.js b/routes/api/sponsor.js index 9d14c9d5..427f573b 100644 --- a/routes/api/sponsor.js +++ b/routes/api/sponsor.js @@ -36,13 +36,21 @@ module.exports = { * @apiSuccessExample {object} Success-Response: * { "message": "Successfully retrieved sponsor information", - "data": {...} + "data": { + "id": "5bff4d736f86be0a41badb91", + "accountId": "5bff4d736f86be0a41badb99", + "tier": 3, + "company": "companyName", + "contractURL": "https://www.contractHere.com", + "nominees": ["5bff4d736f86be0a41badb93","5bff4d736f86be0a41badb94"] + } } * @apiError {String} message Error message * @apiError {Object} data empty * @apiErrorExample {object} Error-Response: * {"message": "Sponsor not found", "data": {}} + * @apiPermission: Sponsor */ sponsorRouter.route("/self").get( Middleware.Auth.ensureAuthenticated(), From 269ba05806c9f58158e804923147dfed3c77136d Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sat, 19 Jan 2019 17:07:36 -0500 Subject: [PATCH 04/14] Add roles in post_roles to respect the tiers of sponsors --- constants/general.constant.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/constants/general.constant.js b/constants/general.constant.js index 77af8a5e..dd0385b9 100644 --- a/constants/general.constant.js +++ b/constants/general.constant.js @@ -69,7 +69,11 @@ const REQUEST_TYPES = { //Define names of the roles specifically associated with permission to create an account const POST_ROLES = {}; POST_ROLES[HACKER] = "postHacker"; -POST_ROLES[SPONSOR] = "postSponsor"; +POST_ROLES[SPONSOR_T1] = "postSponsor"; +POST_ROLES[SPONSOR_T2] = "postSponsor"; +POST_ROLES[SPONSOR_T3] = "postSponsor"; +POST_ROLES[SPONSOR_T4] = "postSponsor"; +POST_ROLES[SPONSOR_T5] = "postSponsor"; POST_ROLES[VOLUNTEER] = "postVolunteer"; POST_ROLES[STAFF] = "postStaff"; From 4641a5c9ea86fe24f65836a3c20319362d4ea50f Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 20 Jan 2019 22:59:54 -0500 Subject: [PATCH 05/14] Add success and error for sponsor patch --- constants/error.constant.js | 2 ++ constants/success.constant.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/constants/error.constant.js b/constants/error.constant.js index c2f38662..4f497044 100644 --- a/constants/error.constant.js +++ b/constants/error.constant.js @@ -34,6 +34,7 @@ const HACKER_UPDATE_500_MESSAGE = "Error while updating hacker"; const ACCOUNT_UPDATE_500_MESSAGE = "Error while updating account"; const HACKER_CREATE_500_MESSAGE = "Error while creating hacker"; const SPONSOR_CREATE_500_MESSAGE = "Error while creating sponsor"; +const SPONSOR_UPDATE_500_MESSAGE = "Error while updating sponsor"; const TEAM_CREATE_500_MESSAGE = "Error while creating team"; const VOLUNTEER_CREATE_500_MESSAGE = "Error while creating volunteer"; const EMAIL_500_MESSAGE = "Error while generating email"; @@ -77,4 +78,5 @@ module.exports = { TEAM_JOIN_SAME_409_MESSAGE: TEAM_JOIN_SAME_409_MESSAGE, TEAM_READ_500_MESSAGE: TEAM_READ_500_MESSAGE, VOLUNTEER_404_MESSAGE: VOLUNTEER_404_MESSAGE, + SPONSOR_UPDATE_500_MESSAGE: SPONSOR_UPDATE_500_MESSAGE, }; \ No newline at end of file diff --git a/constants/success.constant.js b/constants/success.constant.js index 19932f18..0d473a25 100644 --- a/constants/success.constant.js +++ b/constants/success.constant.js @@ -33,6 +33,7 @@ const SEARCH_NO_RESULTS = "Query search successful. No results found."; const SPONSOR_GET_BY_ID = "Sponsor found by id."; const SPONSOR_READ = "Sponsor retrieval successful."; const SPONSOR_CREATE = "Sponsor creation successful."; +const SPONSOR_UPDATE = "Sponsor update successful."; const TEAM_GET_BY_ID = "Team found by id."; const TEAM_CREATE = "Team creation successful."; @@ -77,6 +78,7 @@ module.exports = { SPONSOR_GET_BY_ID: SPONSOR_GET_BY_ID, SPONSOR_CREATE: SPONSOR_CREATE, SPONSOR_READ: SPONSOR_READ, + SPONSOR_UPDATE: SPONSOR_UPDATE, TEAM_GET_BY_ID: TEAM_GET_BY_ID, TEAM_CREATE: TEAM_CREATE, From 5eb6597a8bfa82ea8cdb7c5266d6e64d39dca51f Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 20 Jan 2019 23:02:19 -0500 Subject: [PATCH 06/14] Add sponsor patch routes and roles, remove account roles from hacker --- constants/role.constant.js | 9 +++++---- constants/routes.constant.js | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/constants/role.constant.js b/constants/role.constant.js index 752ca1f3..581f7207 100644 --- a/constants/role.constant.js +++ b/constants/role.constant.js @@ -29,10 +29,6 @@ const hackerRole = { "_id": mongoose.Types.ObjectId.createFromTime(2), "name": Constants.General.HACKER, "routes": [ - Constants.Routes.accountRoutes.getSelf, - Constants.Routes.accountRoutes.getSelfById, - Constants.Routes.accountRoutes.patchSelfById, - Constants.Routes.hackerRoutes.post, Constants.Routes.hackerRoutes.getSelfById, Constants.Routes.hackerRoutes.getSelfByEmail, @@ -70,6 +66,7 @@ const sponsorT1Role = { Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, Constants.Routes.sponsorRoutes.getSelf, + Constants.Routes.sponsorRoutes.patchSelfById, ] }; @@ -80,6 +77,7 @@ const sponsorT2Role = { Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, Constants.Routes.sponsorRoutes.getSelf, + Constants.Routes.sponsorRoutes.patchSelfById, ] }; @@ -90,6 +88,7 @@ const sponsorT3Role = { Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, Constants.Routes.sponsorRoutes.getSelf, + Constants.Routes.sponsorRoutes.patchSelfById, ] }; @@ -100,6 +99,7 @@ const sponsorT4Role = { Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, Constants.Routes.sponsorRoutes.getSelf, + Constants.Routes.sponsorRoutes.patchSelfById, ] }; @@ -110,6 +110,7 @@ const sponsorT5Role = { Constants.Routes.sponsorRoutes.post, Constants.Routes.sponsorRoutes.getSelfById, Constants.Routes.sponsorRoutes.getSelf, + Constants.Routes.sponsorRoutes.patchSelfById, ] }; diff --git a/constants/routes.constant.js b/constants/routes.constant.js index 3f00db2c..f1622d5e 100644 --- a/constants/routes.constant.js +++ b/constants/routes.constant.js @@ -143,6 +143,14 @@ const sponsorRoutes = { requestType: Constants.REQUEST_TYPES.POST, uri: "/api/sponsor/", }, + "patchSelfById": { + requestType: Constants.REQUEST_TYPES.PATCH, + uri: "/api/sponsor/" + Constants.ROLE_CATEGORIES.SELF, + }, + "patchAnyById": { + requestType: Constants.REQUEST_TYPES.PATCH, + uri: "/api/sponsor/" + Constants.ROLE_CATEGORIES.ALL, + } }; const teamRoutes = { From c091f19e05398a4e7cfa080070d3005a300cf76c Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 20 Jan 2019 23:02:38 -0500 Subject: [PATCH 07/14] create updatedSponsor controller with comments --- controllers/sponsor.controller.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/controllers/sponsor.controller.js b/controllers/sponsor.controller.js index 2698b54c..67c03437 100644 --- a/controllers/sponsor.controller.js +++ b/controllers/sponsor.controller.js @@ -24,20 +24,35 @@ function showSponsor(req, res) { } /** - * @function createdHacker + * @function createdSponsor * @param {{body: {sponsor: {_id: ObjectId, accountId: ObjectId, tier: number, company: string, contractURL: string, nominees: ObjectId[]}}}} req * @param {*} res * @return {JSON} Success status * @description returns success message */ -async function createdSponsor(req, res) { +function createdSponsor(req, res) { return res.status(200).json({ message: Constants.Success.SPONSOR_CREATE, data: req.body.sponsor.toJSON() }); } +/** + * @function updatedSponsor + * @param {{body: {sponsor: {_id: ObjectId, accountId: ObjectId, tier: number, company: string, contractURL: string, nominees: ObjectId[]}}}} req + * @param {*} res + * @return {JSON} Success status + * @description returns success message + */ +function updatedSponsor(req, res) { + return res.status(200).json({ + message: Constants.Success.SPONSOR_UPDATE, + data: req.body.sponsor.toJSON() + }); +} + module.exports = { createdSponsor: createdSponsor, showSponsor: showSponsor, + updatedSponsor: updatedSponsor, }; \ No newline at end of file From 5da025357a13991bb09f1385a96ed64f75f2740b Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 20 Jan 2019 23:10:23 -0500 Subject: [PATCH 08/14] Create sponsor patch middleware --- middlewares/sponsor.middleware.js | 42 +++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/middlewares/sponsor.middleware.js b/middlewares/sponsor.middleware.js index 48193523..9c41104f 100644 --- a/middlewares/sponsor.middleware.js +++ b/middlewares/sponsor.middleware.js @@ -11,6 +11,7 @@ const Constants = { General: require("../constants/general.constant"), Error: require("../constants/error.constant"), }; +const Sponsor = require("../models/sponsor.model"); /** * @function parsePatch @@ -18,10 +19,21 @@ const Constants = { * @param {*} res * @param {(err?) => void} next * @return {void} - * @description Delete the req.body.id that was added by the validation of route parameter. + * @description Delete the req.body.id that was added by the validation of route parameter, and put in sponsorDetails */ function parsePatch(req, res, next) { - delete req.body.id; + let sponsorDetails = {}; + + for (const val in req.body) { + // use .hasOwnProperty instead of 'in' to get rid of inherited properties such as 'should' + if (Sponsor.schema.paths.hasOwnProperty(val)) { + sponsorDetails[val] = req.body[val]; + delete req.body[val]; + } + } + + req.body.sponsorDetails = sponsorDetails; + return next(); } @@ -166,6 +178,31 @@ async function createSponsor(req, res, next) { } } +/** + * @async + * @function updateSponsor + * @param {{body: {id: ObjectId, sponsorDetails: {company?: string, contractURL?: string, nominees?: ObjectId[]}}}} req + * @param {*} res + * @param {(err?)=>void} next + * @description Updates a sponsor specified by req.body.id with information specified in req.body.sponsorDetails. + */ +async function updateSponsor(req, res, next) { + const sponsorDetails = req.body.sponsorDetails; + + const sponsor = await Services.Sponsor.updateOne(req.body.id, sponsorDetails); + + if (!!sponsor) { + req.body.sponsor = sponsor; + return next(); + } else { + return next({ + status: 500, + message: Constants.Error.SPONSOR_UPDATE_500_MESSAGE, + data: {} + }); + } +} + /** * Checks that there are no other sponsor with the same account id as the one passed into req.body.accountId * @param {{body:{accountId: ObjectId}}} req @@ -195,4 +232,5 @@ module.exports = { createSponsor: Middleware.Util.asyncMiddleware(createSponsor), checkDuplicateAccountLinks: Middleware.Util.asyncMiddleware(checkDuplicateAccountLinks), validateConfirmedStatus: Middleware.Util.asyncMiddleware(validateConfirmedStatus), + updateSponsor: Middleware.Util.asyncMiddleware(updateSponsor), }; \ No newline at end of file From a16a89d99c3dcb160f0626fc54ce96a3af4a505b Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 20 Jan 2019 23:10:32 -0500 Subject: [PATCH 09/14] create update sponsor validator --- middlewares/validators/sponsor.validator.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/middlewares/validators/sponsor.validator.js b/middlewares/validators/sponsor.validator.js index a3055307..60deab29 100644 --- a/middlewares/validators/sponsor.validator.js +++ b/middlewares/validators/sponsor.validator.js @@ -14,4 +14,10 @@ module.exports = { VALIDATOR.regexValidator("body", "contractURL", false, Constants.URL_REGEX), VALIDATOR.mongoIdArrayValidator("body", "nominees", true), ], + + updateSponsorValidator: [ + VALIDATOR.asciiValidator("body", "company", true), + VALIDATOR.regexValidator("body", "contractURL", true, Constants.URL_REGEX), + VALIDATOR.mongoIdArrayValidator("body", "nominees", true), + ], }; \ No newline at end of file From 3177275aec4de6c7b142dd916e291aa8bc4df234 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 20 Jan 2019 23:12:14 -0500 Subject: [PATCH 10/14] Create sponsor service --- services/sponsor.service.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/services/sponsor.service.js b/services/sponsor.service.js index 55e9e072..4a6ab9b7 100644 --- a/services/sponsor.service.js +++ b/services/sponsor.service.js @@ -31,6 +31,25 @@ function createSponsor(sponsorDetails) { return sponsor.save(); } +/** + * @function updateOne + * @param {ObjectId} id + * @param {{company?: string, contractURL?: string, nominees?: ObjectId[]}} sponsorDetails + * @return {Promise} The promise will resolve to a sponsor object if update was successful. + * @description Updates a sponsor by id with information in sponsorDetails. Return the updated sponsor + */ +function updateOne(id, sponsorDetails) { + const TAG = `[Sponsor Service # updateOne]:`; + + const query = { + _id: id + }; + + return Sponsor.findOneAndUpdate(query, sponsorDetails, { + new: true + }); +} + /** * @function findByAccountId * @param {ObjectId} accountId @@ -50,4 +69,5 @@ module.exports = { findByAccountId: findByAccountId, findById: findById, createSponsor: createSponsor, + updateOne: updateOne, }; \ No newline at end of file From d8f22f665b5245d235d62b563b5d4486fdc7116f Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 20 Jan 2019 23:14:12 -0500 Subject: [PATCH 11/14] Create sponsor patch route and update docs --- docs/api/api_data.js | 99 ++++++++++++++++++++++++++++++++++++++- docs/api/api_data.json | 99 ++++++++++++++++++++++++++++++++++++++- docs/api/api_project.js | 2 +- docs/api/api_project.json | 2 +- routes/api/sponsor.js | 38 +++++++++++++++ 5 files changed, 236 insertions(+), 4 deletions(-) diff --git a/docs/api/api_data.js b/docs/api/api_data.js index a7a4b40e..c3255d7b 100644 --- a/docs/api/api_data.js +++ b/docs/api/api_data.js @@ -1824,7 +1824,7 @@ define({ }, "examples": [{ "title": "Success-Response: ", - "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {...}\n }", + "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {\n \"id\": \"5bff4d736f86be0a41badb91\",\n \"accountId\": \"5bff4d736f86be0a41badb99\",\n \"tier\": 3,\n \"company\": \"companyName\",\n \"contractURL\": \"https://www.contractHere.com\",\n \"nominees\": [\"5bff4d736f86be0a41badb93\",\"5bff4d736f86be0a41badb94\"]\n }\n }", "type": "object" }] }, @@ -1852,6 +1852,9 @@ define({ "type": "object" }] }, + "permission": [{ + "name": ": Sponsor" + }], "filename": "routes/api/sponsor.js", "groupTitle": "Hacker", "sampleRequest": [{ @@ -2314,6 +2317,100 @@ define({ "url": "https://api.mchacks.ca/api/sponsor/:id" }] }, + { + "type": "patch", + "url": "/sponsor/", + "title": "update a sponsor", + "name": "patchSponsor", + "group": "Sponsor", + "version": "1.3.0", + "parameter": { + "fields": { + "param": [{ + "group": "param", + "type": "ObjectId", + "optional": false, + "field": "id", + "description": "

ObjectID of the sponsor

" + }], + "body": [{ + "group": "body", + "type": "String", + "optional": false, + "field": "company", + "description": "

Name of the company.

" + }, + { + "group": "body", + "type": "String", + "optional": false, + "field": "contractURL", + "description": "

URL link to the contract with the company.

" + }, + { + "group": "body", + "type": "ObjectId[]", + "optional": false, + "field": "nominees", + "description": "

Array of accounts that the company wish to nominate as hackers.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [{ + "group": "Success 200", + "type": "String", + "optional": false, + "field": "message", + "description": "

Success message

" + }, + { + "group": "Success 200", + "type": "Object", + "optional": false, + "field": "data", + "description": "

Sponsor object

" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Sponsor update successful\", \n \"data\": {...}\n }", + "type": "object" + }] + }, + "error": { + "fields": { + "Error 4xx": [{ + "group": "Error 4xx", + "type": "String", + "optional": false, + "field": "message", + "description": "

Error message

" + }, + { + "group": "Error 4xx", + "type": "Object", + "optional": false, + "field": "data", + "description": "

empty

" + } + ] + }, + "examples": [{ + "title": "Error-Response: ", + "content": "{\"message\": \"Error while updating sponsor\", \"data\": {}}", + "type": "object" + }] + }, + "filename": "routes/api/sponsor.js", + "groupTitle": "Sponsor", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/sponsor/" + }] + }, { "type": "post", "url": "/team/", diff --git a/docs/api/api_data.json b/docs/api/api_data.json index 6a1d94b3..fe958503 100644 --- a/docs/api/api_data.json +++ b/docs/api/api_data.json @@ -1823,7 +1823,7 @@ }, "examples": [{ "title": "Success-Response: ", - "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {...}\n }", + "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {\n \"id\": \"5bff4d736f86be0a41badb91\",\n \"accountId\": \"5bff4d736f86be0a41badb99\",\n \"tier\": 3,\n \"company\": \"companyName\",\n \"contractURL\": \"https://www.contractHere.com\",\n \"nominees\": [\"5bff4d736f86be0a41badb93\",\"5bff4d736f86be0a41badb94\"]\n }\n }", "type": "object" }] }, @@ -1851,6 +1851,9 @@ "type": "object" }] }, + "permission": [{ + "name": ": Sponsor" + }], "filename": "routes/api/sponsor.js", "groupTitle": "Hacker", "sampleRequest": [{ @@ -2313,6 +2316,100 @@ "url": "https://api.mchacks.ca/api/sponsor/:id" }] }, + { + "type": "patch", + "url": "/sponsor/", + "title": "update a sponsor", + "name": "patchSponsor", + "group": "Sponsor", + "version": "1.3.0", + "parameter": { + "fields": { + "param": [{ + "group": "param", + "type": "ObjectId", + "optional": false, + "field": "id", + "description": "

ObjectID of the sponsor

" + }], + "body": [{ + "group": "body", + "type": "String", + "optional": false, + "field": "company", + "description": "

Name of the company.

" + }, + { + "group": "body", + "type": "String", + "optional": false, + "field": "contractURL", + "description": "

URL link to the contract with the company.

" + }, + { + "group": "body", + "type": "ObjectId[]", + "optional": false, + "field": "nominees", + "description": "

Array of accounts that the company wish to nominate as hackers.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [{ + "group": "Success 200", + "type": "String", + "optional": false, + "field": "message", + "description": "

Success message

" + }, + { + "group": "Success 200", + "type": "Object", + "optional": false, + "field": "data", + "description": "

Sponsor object

" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Sponsor update successful\", \n \"data\": {...}\n }", + "type": "object" + }] + }, + "error": { + "fields": { + "Error 4xx": [{ + "group": "Error 4xx", + "type": "String", + "optional": false, + "field": "message", + "description": "

Error message

" + }, + { + "group": "Error 4xx", + "type": "Object", + "optional": false, + "field": "data", + "description": "

empty

" + } + ] + }, + "examples": [{ + "title": "Error-Response: ", + "content": "{\"message\": \"Error while updating sponsor\", \"data\": {}}", + "type": "object" + }] + }, + "filename": "routes/api/sponsor.js", + "groupTitle": "Sponsor", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/sponsor/" + }] + }, { "type": "post", "url": "/team/", diff --git a/docs/api/api_project.js b/docs/api/api_project.js index 3b66e564..278259a4 100644 --- a/docs/api/api_project.js +++ b/docs/api/api_project.js @@ -9,7 +9,7 @@ define({ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2019-01-18T19:02:31.941Z", + "time": "2019-01-21T04:13:36.482Z", "url": "http://apidocjs.com", "version": "0.17.7" } diff --git a/docs/api/api_project.json b/docs/api/api_project.json index 8c20a67c..13eedf19 100644 --- a/docs/api/api_project.json +++ b/docs/api/api_project.json @@ -9,7 +9,7 @@ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2019-01-18T19:02:31.941Z", + "time": "2019-01-21T04:13:36.482Z", "url": "http://apidocjs.com", "version": "0.17.7" } diff --git a/routes/api/sponsor.js b/routes/api/sponsor.js index 427f573b..48d58910 100644 --- a/routes/api/sponsor.js +++ b/routes/api/sponsor.js @@ -139,6 +139,44 @@ module.exports = { Controllers.Sponsor.createdSponsor ); + /** + * @api {patch} /sponsor/ update a sponsor + * @apiName patchSponsor + * @apiGroup Sponsor + * @apiVersion 1.3.0 + * + * @apiParam (param) {ObjectId} id ObjectID of the sponsor + * @apiParam (body) {String} company Name of the company. + * @apiParam (body) {String} contractURL URL link to the contract with the company. + * @apiParam (body) {ObjectId[]} nominees Array of accounts that the company wish to nominate as hackers. + * + * @apiSuccess {String} message Success message + * @apiSuccess {Object} data Sponsor object + * @apiSuccessExample {object} Success-Response: + * { + "message": "Sponsor update successful", + "data": {...} + } + + * @apiError {String} message Error message + * @apiError {Object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Error while updating sponsor", "data": {}} + */ + sponsorRouter.route("/:id").patch( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Sponsor.findById]), + + Middleware.Validator.RouteParam.idValidator, + Middleware.Validator.Sponsor.updateSponsorValidator, + + Middleware.parseBody.middleware, + Middleware.Sponsor.parsePatch, + + Middleware.Sponsor.updateSponsor, + Controllers.Sponsor.updatedSponsor + ); + apiRouter.use("/sponsor", sponsorRouter); } }; \ No newline at end of file From d86bdfcfe345153bd85a841995677227878992f7 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Sun, 20 Jan 2019 23:14:34 -0500 Subject: [PATCH 12/14] sponsor test --- tests/sponsor.test.js | 93 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/sponsor.test.js b/tests/sponsor.test.js index d87dd584..f944d866 100644 --- a/tests/sponsor.test.js +++ b/tests/sponsor.test.js @@ -339,6 +339,99 @@ describe("POST create sponsor", function () { res.body.message.should.equal(Constants.Error.AUTH_403_MESSAGE); res.body.should.have.property("data"); + done(); + }); + }); + }); +}); + +describe("PATCH update sponsor", function () { + it("should fail to update a sponsor due to lack of authentication", function (done) { + chai.request(server.app) + .patch(`/api/sponsor/${T1Sponsor0._id}/`) + .type("application/json") + .send({ + company: "NewCompanyName" + }) + .end(function (err, res) { + res.should.have.status(401); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal(Constants.Error.AUTH_401_MESSAGE); + + done(); + }); + }); + + it("should FAIL to update a sponsor due to authorization", function (done) { + util.auth.login(agent, newT2SponsorAccount0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .patch(`/api/sponsor/${T1Sponsor0._id}/`) + .type("application/json") + .send({ + company: "NewCompanyName" + }) + .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 FAIL to update a sponsor due wrong id", function (done) { + util.auth.login(agent, Admin0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .patch(`/api/sponsor/${Admin0._id}/`) + .type("application/json") + .send({ + company: "NewCompanyName" + }) + .end(function (err, res) { + res.should.have.status(500); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal(Constants.Error.SPONSOR_UPDATE_500_MESSAGE); + + done(); + }); + }); + }); + + // success case with self caes - there is no admin case + it("should SUCCEED and update a sponsor", function (done) { + util.auth.login(agent, T1SponsorAccount0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .patch(`/api/sponsor/${T1Sponsor0._id}/`) + .type("application/json") + .send({ + company: "NewCompanyName" + }) + .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.SPONSOR_UPDATE); + res.body.should.have.property("data"); + + res.body.data.should.have.property("company"); + res.body.data.company.should.equal("NewCompanyName"); + done(); }); }); From a0700bd6d99cb0ce2deb9497854adeb54f1de13e Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Mon, 21 Jan 2019 11:39:35 -0500 Subject: [PATCH 13/14] Fix merge issue --- docs/api/api_data.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/api/api_data.json b/docs/api/api_data.json index cbf99d08..8e1fe2c5 100644 --- a/docs/api/api_data.json +++ b/docs/api/api_data.json @@ -1823,13 +1823,7 @@ }, "examples": [{ "title": "Success-Response: ", - << - << << < HEAD "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {\n \"id\": \"5bff4d736f86be0a41badb91\",\n \"accountId\": \"5bff4d736f86be0a41badb99\",\n \"tier\": 3,\n \"company\": \"companyName\",\n \"contractURL\": \"https://www.contractHere.com\",\n \"nominees\": [\"5bff4d736f86be0a41badb93\",\"5bff4d736f86be0a41badb94\"]\n }\n }", - === - === = - "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {...}\n }", - >>> - >>> > develop "type": "object" + "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {\n \"id\": \"5bff4d736f86be0a41badb91\",\n \"accountId\": \"5bff4d736f86be0a41badb99\",\n \"tier\": 3,\n \"company\": \"companyName\",\n \"contractURL\": \"https://www.contractHere.com\",\n \"nominees\": [\"5bff4d736f86be0a41badb93\",\"5bff4d736f86be0a41badb94\"]\n }\n }" }] }, "error": { From 15bbc0ba3eedee9750141070912c4514ad913950 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Mon, 21 Jan 2019 14:10:00 -0500 Subject: [PATCH 14/14] PR comments --- middlewares/sponsor.middleware.js | 2 +- routes/api/sponsor.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/middlewares/sponsor.middleware.js b/middlewares/sponsor.middleware.js index 9c41104f..339ada3d 100644 --- a/middlewares/sponsor.middleware.js +++ b/middlewares/sponsor.middleware.js @@ -19,7 +19,7 @@ const Sponsor = require("../models/sponsor.model"); * @param {*} res * @param {(err?) => void} next * @return {void} - * @description Delete the req.body.id that was added by the validation of route parameter, and put in sponsorDetails + * @description Put relevent sponsor attributes into sponsorDetails */ function parsePatch(req, res, next) { let sponsorDetails = {}; diff --git a/routes/api/sponsor.js b/routes/api/sponsor.js index 48d58910..91d8379c 100644 --- a/routes/api/sponsor.js +++ b/routes/api/sponsor.js @@ -73,7 +73,14 @@ module.exports = { * @apiSuccessExample {object} Success-Response: * { "message": "Successfully retrieved sponsor information", - "data": {...} + "data": { + "id": "5bff4d736f86be0a41badb91", + "accountId": "5bff4d736f86be0a41badb99", + "tier": 3, + "company": "companyName", + "contractURL": "https://www.contractHere.com", + "nominees": ["5bff4d736f86be0a41badb93","5bff4d736f86be0a41badb94"] + } } * @apiError {String} message Error message