diff --git a/constants/error.constant.js b/constants/error.constant.js index 7ecc0d40..c2f38662 100644 --- a/constants/error.constant.js +++ b/constants/error.constant.js @@ -5,6 +5,7 @@ const HACKER_404_MESSAGE = "Hacker not found"; const TEAM_404_MESSAGE = "Team not found"; const RESUME_404_MESSAGE = "Resume not found"; const SPONSOR_404_MESSAGE = "Sponsor not found"; +const VOLUNTEER_404_MESSAGE = "Volunteer not found"; const ACCOUNT_TYPE_409_MESSAGE = "Wrong account type"; const SPONSOR_ID_409_MESSAGE = "Conflict with sponsor accountId link"; @@ -75,4 +76,5 @@ module.exports = { TEAM_NAME_409_MESSAGE: TEAM_NAME_409_MESSAGE, TEAM_JOIN_SAME_409_MESSAGE: TEAM_JOIN_SAME_409_MESSAGE, TEAM_READ_500_MESSAGE: TEAM_READ_500_MESSAGE, + VOLUNTEER_404_MESSAGE: VOLUNTEER_404_MESSAGE, }; \ No newline at end of file diff --git a/constants/role.constant.js b/constants/role.constant.js index 89cf78eb..0fafb834 100644 --- a/constants/role.constant.js +++ b/constants/role.constant.js @@ -52,6 +52,7 @@ const volunteerRole = { "_id": mongoose.Types.ObjectId.createFromTime(3), "name": Constants.General.VOLUNTEER, "routes": [ + Constants.Routes.volunteerRoutes.getSelfById, Constants.Routes.volunteerRoutes.post, Constants.Routes.hackerRoutes.patchAnyCheckInById, diff --git a/constants/routes.constant.js b/constants/routes.constant.js index 74fa8806..0ae6cdf1 100644 --- a/constants/routes.constant.js +++ b/constants/routes.constant.js @@ -161,6 +161,14 @@ const teamRoutes = { }; const volunteerRoutes = { + "getSelfById": { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/volunteer/" + Constants.ROLE_CATEGORIES.SELF, + }, + "getAnyById": { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/volunteer/" + Constants.ROLE_CATEGORIES.ALL, + }, "post": { requestType: Constants.REQUEST_TYPES.POST, uri: "/api/volunteer/", diff --git a/constants/success.constant.js b/constants/success.constant.js index 4076111e..19932f18 100644 --- a/constants/success.constant.js +++ b/constants/success.constant.js @@ -41,6 +41,7 @@ const TEAM_UPDATE = "Team update successful."; const TEAM_READ = "Team retrieval successful."; const TEAM_HACKER_LEAVE = "Removal from team successful."; +const VOLUNTEER_GET_BY_ID = "Volunteer found by id"; const VOLUNTEER_CREATE = "Volunteer creation successful."; module.exports = { @@ -84,5 +85,6 @@ module.exports = { TEAM_READ: TEAM_READ, TEAM_HACKER_LEAVE: TEAM_HACKER_LEAVE, + VOLUNTEER_GET_BY_ID: VOLUNTEER_GET_BY_ID, VOLUNTEER_CREATE: VOLUNTEER_CREATE, }; \ No newline at end of file diff --git a/controllers/volunteer.controller.js b/controllers/volunteer.controller.js index 8019eec3..b395308b 100644 --- a/controllers/volunteer.controller.js +++ b/controllers/volunteer.controller.js @@ -10,21 +10,35 @@ const Constants = { } /** - * @async * @function createdVolunteer * @param {{body: {volunteer: {_id: ObjectId, accountId: ObjectId}}}} req * @param {*} res - * @return {JSON} Success or error status + * @return {JSON} Success status * @description Show the success message and the created volunteer */ -async function createdVolunteer(req, res) { - +function createdVolunteer(req, res) { return res.status(200).json({ message: Constants.Success.VOLUNTEER_CREATE, data: req.body.volunteer }); } +/** + * @function showVolunteer + * @param {{body: {volunteer: {_id: ObjectId, accountId: ObjectId}}}} req + * @param {*} res + * @return {JSON} Success status + * @description Show the success message and retrieved volunteer + */ +function showVolunteer(req, res) { + return res.status(200).json({ + message: Constants.Success.VOLUNTEER_GET_BY_ID, + data: req.body.volunteer.toJSON() + }); +} + + module.exports = { createdVolunteer: createdVolunteer, + showVolunteer: showVolunteer, }; \ No newline at end of file diff --git a/docs/api/api_data.js b/docs/api/api_data.js index 7af97641..b0ed02e6 100644 --- a/docs/api/api_data.js +++ b/docs/api/api_data.js @@ -2425,6 +2425,7 @@ define({ "name": "patchTeam", "group": "Team", "version": "0.0.8", + "description": "
We use hackerId instead of teamId because authorization requires a one-to-one mapping from param id to accountId, but we are not able to have that from teamId to accountId due to multiple members in a team. Instead, we use hackerId, as there is a 1 to 1 link between hackerId to teamId, and a 1 to 1 link between hackerId and accountId
", "parameter": { "fields": { "param": [{ @@ -2561,6 +2562,78 @@ define({ "sampleRequest": [{ "url": "https://api.mchacks.ca/api/volunteer/" }] + }, + { + "type": "get", + "url": "/volunteer/:id", + "title": "get a volunteer's information", + "name": "getVolunteer", + "group": "Volunteer", + "version": "1.3.0", + "parameter": { + "fields": { + "param": [{ + "group": "param", + "type": "ObjectId", + "optional": false, + "field": "id", + "description": "a volunteer's unique mongoID
" + }] + } + }, + "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": "Volunteer object
" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Successfully retrieved volunteer 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\": \"Volunteer not found\", \"data\": {}}", + "type": "object" + }] + }, + "filename": "routes/api/volunteer.js", + "groupTitle": "Volunteer", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/volunteer/:id" + }] } ] }); \ No newline at end of file diff --git a/docs/api/api_data.json b/docs/api/api_data.json index 03cc58e8..200690d7 100644 --- a/docs/api/api_data.json +++ b/docs/api/api_data.json @@ -2424,6 +2424,7 @@ "name": "patchTeam", "group": "Team", "version": "0.0.8", + "description": "We use hackerId instead of teamId because authorization requires a one-to-one mapping from param id to accountId, but we are not able to have that from teamId to accountId due to multiple members in a team. Instead, we use hackerId, as there is a 1 to 1 link between hackerId to teamId, and a 1 to 1 link between hackerId and accountId
", "parameter": { "fields": { "param": [{ @@ -2560,5 +2561,77 @@ "sampleRequest": [{ "url": "https://api.mchacks.ca/api/volunteer/" }] + }, + { + "type": "get", + "url": "/volunteer/:id", + "title": "get a volunteer's information", + "name": "getVolunteer", + "group": "Volunteer", + "version": "1.3.0", + "parameter": { + "fields": { + "param": [{ + "group": "param", + "type": "ObjectId", + "optional": false, + "field": "id", + "description": "a volunteer's unique mongoID
" + }] + } + }, + "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": "Volunteer object
" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Successfully retrieved volunteer 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\": \"Volunteer not found\", \"data\": {}}", + "type": "object" + }] + }, + "filename": "routes/api/volunteer.js", + "groupTitle": "Volunteer", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/volunteer/:id" + }] } ] \ No newline at end of file diff --git a/docs/api/api_project.js b/docs/api/api_project.js index 22cdc45c..049ebd48 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-08T22:07:07.661Z", + "time": "2019-01-10T02:53:36.684Z", "url": "http://apidocjs.com", "version": "0.17.7" } diff --git a/docs/api/api_project.json b/docs/api/api_project.json index 543c38d6..bc9db6de 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-08T22:07:07.661Z", + "time": "2019-01-10T02:53:36.684Z", "url": "http://apidocjs.com", "version": "0.17.7" } diff --git a/middlewares/volunteer.middleware.js b/middlewares/volunteer.middleware.js index f1376c79..225f1361 100644 --- a/middlewares/volunteer.middleware.js +++ b/middlewares/volunteer.middleware.js @@ -109,9 +109,34 @@ async function validateConfirmedStatus(req, res, next) { } } +/** + * @async + * @function findById + * @param {{body: {id: ObjectId}}} req + * @param {*} res + * @description Retrieves a volunteer's information via req.body.id, moving result to req.body.volunteer if succesful. + */ +async function findById(req, res, next) { + const volunteer = await Services.Volunteer.findById(req.body.id); + + if (!volunteer) { + return next({ + status: 404, + message: Constants.Error.VOLUNTEER_404_MESSAGE, + data: { + id: req.body.id, + } + }); + } + + req.body.volunteer = volunteer; + next(); +} + module.exports = { parseVolunteer: parseVolunteer, createVolunteer: Middleware.Util.asyncMiddleware(createVolunteer), checkDuplicateAccountLinks: Middleware.Util.asyncMiddleware(checkDuplicateAccountLinks), validateConfirmedStatus: Middleware.Util.asyncMiddleware(validateConfirmedStatus), + findById: Middleware.Util.asyncMiddleware(findById), }; \ No newline at end of file diff --git a/routes/api/volunteer.js b/routes/api/volunteer.js index 4749493a..c8ee237c 100644 --- a/routes/api/volunteer.js +++ b/routes/api/volunteer.js @@ -7,7 +7,8 @@ const Controllers = { const Middleware = { Validator: { /* Insert the require statement to the validator file here */ - Volunteer: require("../../middlewares/validators/volunteer.validator") + Volunteer: require("../../middlewares/validators/volunteer.validator"), + RouteParam: require("../../middlewares/validators/routeParam.validator"), }, /* Insert all of ther middleware require statements here */ parseBody: require("../../middlewares/parse-body.middleware"), @@ -24,6 +25,38 @@ module.exports = { activate: function (apiRouter) { const volunteerRouter = express.Router(); + /** + * @api {get} /volunteer/:id get a volunteer's information + * @apiName getVolunteer + * @apiGroup Volunteer + * @apiVersion 1.3.0 + * + * @apiParam (param) {ObjectId} id a volunteer's unique mongoID + * + * @apiSuccess {String} message Success message + * @apiSuccess {Object} data Volunteer object + * @apiSuccessExample {object} Success-Response: + * { + "message": "Successfully retrieved volunteer information", + "data": {...} + } + + * @apiError {String} message Error message + * @apiError {Object} data empty + * @apiErrorExample {object} Error-Response: + * {"message": "Volunteer not found", "data": {}} + */ + volunteerRouter.route("/:id").get( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized([Services.Volunteer.findById]), + + Middleware.Validator.RouteParam.idValidator, + Middleware.parseBody.middleware, + + Middleware.Volunteer.findById, + Controllers.Volunteer.showVolunteer + ); + /** * @api {post} /volunteer/ create a new volunteer * @apiName createVolunteer diff --git a/services/volunteer.service.js b/services/volunteer.service.js index debe80ee..dfd65056 100644 --- a/services/volunteer.service.js +++ b/services/volunteer.service.js @@ -40,7 +40,7 @@ function findByAccountId(accountId) { accountId: accountId }; - return Volunteer.findOne(query, logger.updateCallbackFactory(TAG, "hacker")); + return Volunteer.findOne(query, logger.updateCallbackFactory(TAG, "volunteer")); } module.exports = { diff --git a/tests/volunteer.test.js b/tests/volunteer.test.js index 8f324e36..dcb2a39f 100644 --- a/tests/volunteer.test.js +++ b/tests/volunteer.test.js @@ -26,6 +26,115 @@ const duplicateVolunteer = util.volunteer.duplicateVolunteer1; const hackerAccount = util.account.Account1; +describe("GET volunteer", function () { + it("should FAIL to get volunteer due to lack of authentication", function (done) { + chai.request(server.app) + .get(`/api/volunteer/${util.volunteer.Volunteer1._id}`) + .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); + res.body.should.have.property("data"); + + done(); + }); + }); + + it("should Fail to GET volunteer due inappropriate authorization", function (done) { + util.auth.login(agent, util.account.Hacker5, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get(`/api/volunteer/${util.volunteer.Volunteer1._id}`) + .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); + res.body.should.have.property("data"); + + done(); + }); + }); + }); + + it("should Fail to GET volunteer due to non existant volunteer id", function (done) { + util.auth.login(agent, util.account.Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get(`/api/volunteer/${util.account.Admin1._id}`) + .end(function (err, res) { + res.should.have.status(404); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal(Constants.Error.VOLUNTEER_404_MESSAGE); + res.body.should.have.property("data"); + + done(); + }); + }); + }); + + // success case + it("should GET volunteer info by id with admin credentials", function (done) { + util.auth.login(agent, util.account.Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get(`/api/volunteer/${util.volunteer.Volunteer1._id}`) + .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.VOLUNTEER_GET_BY_ID); + res.body.should.have.property("data"); + + let volunteer = new Volunteer(util.volunteer.Volunteer1); + chai.assert.equal(JSON.stringify(res.body.data), JSON.stringify(volunteer.toJSON())); + done(); + }); + }); + }); + + // success case + it("should GET the user's volunteer info by id", function (done) { + util.auth.login(agent, util.account.Account4, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get(`/api/volunteer/${util.volunteer.Volunteer1._id}`) + .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.VOLUNTEER_GET_BY_ID); + res.body.should.have.property("data"); + + let volunteer = new Volunteer(util.volunteer.Volunteer1); + chai.assert.equal(JSON.stringify(res.body.data), JSON.stringify(volunteer.toJSON())); + done(); + }); + }); + }); + +}); + describe("POST create volunteer", function () { it("should fail to create a new volunteer due to lack of authentication", function (done) { chai.request(server.app)