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/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 = { 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, 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 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..8e1fe2c5 100644 --- a/docs/api/api_data.json +++ b/docs/api/api_data.json @@ -1823,8 +1823,7 @@ }, "examples": [{ "title": "Success-Response: ", - "content": "{\n \"message\": \"Successfully retrieved sponsor information\", \n \"data\": {...}\n }", - "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": { @@ -1851,6 +1850,9 @@ "type": "object" }] }, + "permission": [{ + "name": ": Sponsor" + }], "filename": "routes/api/sponsor.js", "groupTitle": "Hacker", "sampleRequest": [{ @@ -2313,6 +2315,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..7284ad8f 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-18T19:02:31.941Z", - "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-21T04:13:36.482Z", + "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 8c20a67c..1984421d 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-18T19:02:31.941Z", - "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-21T04:13:36.482Z", + "url": "http://apidocjs.com", + "version": "0.17.7" + } } \ No newline at end of file diff --git a/middlewares/sponsor.middleware.js b/middlewares/sponsor.middleware.js index 48193523..339ada3d 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 Put relevent sponsor attributes into 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 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 diff --git a/routes/api/sponsor.js b/routes/api/sponsor.js index 427f573b..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 @@ -139,6 +146,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 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 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(); }); });